@oh-my-pi/pi-coding-agent 15.12.4 → 15.13.0
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 +304 -6
- package/dist/cli.js +1015 -881
- package/dist/types/async/job-manager.d.ts +15 -0
- package/dist/types/autolearn/controller.d.ts +25 -0
- package/dist/types/autolearn/managed-skills.d.ts +45 -0
- package/dist/types/autoresearch/state.d.ts +1 -1
- package/dist/types/autoresearch/types.d.ts +1 -1
- package/dist/types/cli/args.d.ts +19 -1
- package/dist/types/cli/session-picker.d.ts +1 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/cli/setup-model-picker.d.ts +14 -0
- package/dist/types/collab/protocol.d.ts +1 -1
- package/dist/types/commands/say.d.ts +24 -0
- package/dist/types/config/keybindings.d.ts +3 -3
- package/dist/types/config/model-registry.d.ts +10 -0
- package/dist/types/config/models-config-schema.d.ts +12 -0
- package/dist/types/config/models-config.d.ts +8 -2
- package/dist/types/config/settings-schema.d.ts +261 -58
- package/dist/types/export/html/index.d.ts +2 -1
- package/dist/types/extensibility/extensions/model-api.d.ts +17 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +47 -1
- package/dist/types/extensibility/hooks/index.d.ts +2 -1
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +9 -0
- package/dist/types/extensibility/plugins/loader.d.ts +11 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/extensibility/skills.d.ts +10 -0
- package/dist/types/goals/guided-setup.d.ts +18 -0
- package/dist/types/goals/state.d.ts +1 -1
- package/dist/types/hindsight/transcript.d.ts +1 -1
- package/dist/types/index.d.ts +5 -0
- package/dist/types/internal-urls/local-protocol.d.ts +4 -2
- package/dist/types/main.d.ts +4 -3
- package/dist/types/mcp/startup-events.d.ts +11 -0
- package/dist/types/memories/index.d.ts +7 -0
- package/dist/types/memory-backend/local-backend.d.ts +4 -3
- package/dist/types/mnemopi/config.d.ts +4 -4
- package/dist/types/modes/components/agent-hub.d.ts +6 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/compaction-summary-message.d.ts +15 -1
- package/dist/types/modes/components/custom-editor.d.ts +39 -1
- package/dist/types/modes/components/custom-editor.test.d.ts +1 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/tool-execution.d.ts +26 -16
- package/dist/types/modes/components/transcript-container.d.ts +23 -2
- package/dist/types/modes/components/tree-selector.d.ts +1 -1
- package/dist/types/modes/components/usage-row.d.ts +3 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -2
- package/dist/types/modes/controllers/input-controller.d.ts +14 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
- package/dist/types/modes/gradient-highlight.d.ts +9 -4
- package/dist/types/modes/image-references.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +27 -3
- package/dist/types/modes/magic-keywords.d.ts +13 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +35 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +9 -1
- package/dist/types/modes/runtime-init.d.ts +4 -0
- package/dist/types/modes/theme/theme.d.ts +13 -2
- package/dist/types/modes/types.d.ts +8 -2
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
- package/dist/types/registry/agent-registry.d.ts +17 -0
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +14 -2
- package/dist/types/session/indexed-session-storage.d.ts +3 -4
- package/dist/types/session/session-context.d.ts +39 -0
- package/dist/types/session/session-entries.d.ts +159 -0
- package/dist/types/session/session-listing.d.ts +69 -0
- package/dist/types/session/session-loader.d.ts +16 -0
- package/dist/types/session/session-manager.d.ts +82 -474
- package/dist/types/session/session-migrations.d.ts +12 -0
- package/dist/types/session/session-paths.d.ts +25 -0
- package/dist/types/session/session-persistence.d.ts +8 -0
- package/dist/types/session/session-storage.d.ts +11 -12
- package/dist/types/session/snapcompact-inline.d.ts +12 -1
- package/dist/types/session/snapcompact-savings-journal.d.ts +46 -0
- package/dist/types/session/tool-choice-queue.d.ts +6 -6
- package/dist/types/stt/asr-client.d.ts +90 -0
- package/dist/types/stt/asr-protocol.d.ts +97 -0
- package/dist/types/stt/asr-worker.d.ts +2 -0
- package/dist/types/stt/downloader.d.ts +38 -0
- package/dist/types/stt/endpointer.d.ts +59 -0
- package/dist/types/stt/index.d.ts +5 -1
- package/dist/types/stt/models.d.ts +120 -0
- package/dist/types/stt/recorder.d.ts +17 -0
- package/dist/types/stt/stt-controller.d.ts +6 -0
- package/dist/types/stt/transcriber.d.ts +5 -7
- package/dist/types/stt/wav.d.ts +29 -0
- package/dist/types/system-prompt.d.ts +4 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/task/index.d.ts +9 -1
- package/dist/types/task/types.d.ts +36 -0
- package/dist/types/tools/bash.d.ts +2 -2
- package/dist/types/tools/eval-render.d.ts +1 -1
- package/dist/types/tools/index.d.ts +11 -1
- package/dist/types/tools/irc.d.ts +1 -0
- package/dist/types/tools/learn.d.ts +51 -0
- package/dist/types/tools/manage-skill.d.ts +40 -0
- package/dist/types/tools/plan-mode-guard.d.ts +10 -0
- package/dist/types/tools/renderers.d.ts +7 -11
- package/dist/types/tools/ssh.d.ts +1 -1
- package/dist/types/tools/todo.d.ts +1 -1
- package/dist/types/tools/tts.d.ts +25 -0
- package/dist/types/tools/write.d.ts +1 -1
- package/dist/types/tts/downloader.d.ts +20 -0
- package/dist/types/tts/index.d.ts +8 -0
- package/dist/types/tts/models.d.ts +82 -0
- package/dist/types/tts/player.d.ts +32 -0
- package/dist/types/tts/runtime.d.ts +6 -0
- package/dist/types/tts/streaming-player.d.ts +41 -0
- package/dist/types/tts/tts-client.d.ts +93 -0
- package/dist/types/tts/tts-protocol.d.ts +95 -0
- package/dist/types/tts/tts-worker.d.ts +2 -0
- package/dist/types/tts/vocalizer.d.ts +41 -0
- package/dist/types/tts/wav.d.ts +8 -0
- package/dist/types/utils/tool-choice.d.ts +8 -0
- package/dist/types/utils/tools-manager.d.ts +2 -1
- package/dist/types/utils/tools-manager.test.d.ts +1 -0
- package/dist/types/web/scrapers/github.d.ts +1 -1
- package/package.json +15 -14
- package/src/async/job-manager.ts +49 -0
- package/src/autolearn/controller.ts +139 -0
- package/src/autolearn/managed-skills.ts +257 -0
- package/src/autoresearch/state.ts +1 -1
- package/src/autoresearch/types.ts +1 -1
- package/src/cli/args.ts +56 -2
- package/src/cli/session-picker.ts +2 -1
- package/src/cli/setup-cli.ts +148 -47
- package/src/cli/setup-model-picker.ts +43 -0
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +45 -13
- package/src/collab/host.ts +1 -1
- package/src/collab/protocol.ts +1 -1
- package/src/commands/say.ts +102 -0
- package/src/commands/setup.ts +1 -1
- package/src/commit/agentic/tools/analyze-file.ts +3 -0
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-discovery.ts +11 -5
- package/src/config/model-registry.ts +64 -9
- package/src/config/models-config-schema.ts +4 -1
- package/src/config/models-config.ts +2 -1
- package/src/config/settings-schema.ts +248 -32
- package/src/config/settings.ts +10 -0
- package/src/discovery/builtin.ts +23 -1
- package/src/discovery/claude-plugins.ts +44 -5
- package/src/discovery/helpers.ts +41 -1
- package/src/eval/__tests__/budget-bridge.test.ts +1 -1
- package/src/eval/js/shared/prelude.txt +69 -17
- package/src/export/html/index.ts +3 -6
- package/src/extensibility/extensions/model-api.ts +41 -0
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +52 -1
- package/src/extensibility/extensions/wrapper.ts +41 -5
- package/src/extensibility/hooks/index.ts +2 -1
- package/src/extensibility/plugins/legacy-pi-compat.ts +43 -13
- package/src/extensibility/plugins/loader.ts +30 -19
- package/src/extensibility/plugins/manager.ts +221 -90
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skills.ts +96 -15
- package/src/goals/guided-setup.ts +133 -0
- package/src/goals/state.ts +1 -1
- package/src/hindsight/transcript.ts +1 -1
- package/src/index.ts +5 -0
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/internal-urls/history-protocol.ts +1 -1
- package/src/internal-urls/local-protocol.ts +29 -7
- package/src/main.ts +27 -7
- package/src/mcp/startup-events.ts +21 -0
- package/src/mcp/transports/stdio.ts +2 -1
- package/src/memories/index.ts +146 -11
- package/src/memory-backend/local-backend.ts +11 -5
- package/src/mnemopi/backend.ts +1 -0
- package/src/mnemopi/config.ts +26 -10
- package/src/modes/acp/acp-agent.ts +3 -5
- package/src/modes/components/agent-hub.ts +49 -4
- package/src/modes/components/assistant-message.ts +4 -37
- package/src/modes/components/compaction-summary-message.ts +125 -26
- package/src/modes/components/custom-editor.test.ts +96 -0
- package/src/modes/components/custom-editor.ts +164 -8
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/settings-defs.ts +7 -0
- package/src/modes/components/tool-execution.ts +82 -43
- package/src/modes/components/transcript-container.ts +70 -1
- package/src/modes/components/tree-selector.ts +1 -1
- package/src/modes/components/usage-row.ts +18 -0
- package/src/modes/components/user-message.ts +4 -2
- package/src/modes/controllers/command-controller.ts +14 -4
- package/src/modes/controllers/event-controller.ts +78 -11
- package/src/modes/controllers/extension-ui-controller.ts +6 -0
- package/src/modes/controllers/input-controller.ts +258 -27
- package/src/modes/controllers/selector-controller.ts +12 -2
- package/src/modes/gradient-highlight.ts +21 -9
- package/src/modes/image-references.ts +20 -0
- package/src/modes/interactive-mode.ts +286 -40
- package/src/modes/magic-keywords.ts +27 -5
- package/src/modes/rpc/rpc-mode.ts +146 -14
- package/src/modes/rpc/rpc-subagents.ts +2 -2
- package/src/modes/rpc/rpc-types.ts +8 -2
- package/src/modes/runtime-init.ts +28 -3
- package/src/modes/theme/theme.ts +98 -50
- package/src/modes/types.ts +6 -2
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/modes/utils/ui-helpers.ts +34 -6
- package/src/priority.json +5 -1
- package/src/prompts/agents/task.md +1 -0
- package/src/prompts/goals/guided-goal-interview.md +8 -0
- package/src/prompts/goals/guided-goal-system.md +12 -0
- package/src/prompts/memories/read-path.md +6 -0
- package/src/prompts/system/autolearn-guidance-learn.md +1 -0
- package/src/prompts/system/autolearn-guidance.md +7 -0
- package/src/prompts/system/autolearn-nudge.md +3 -0
- package/src/prompts/system/eager-task.md +7 -0
- package/src/prompts/system/eager-todo.md +11 -6
- package/src/prompts/system/subagent-system-prompt.md +4 -0
- package/src/prompts/system/system-prompt.md +10 -5
- package/src/prompts/system/title-marker-instruction.md +1 -0
- package/src/prompts/system/title-system-marker.md +16 -0
- package/src/prompts/tools/job.md +1 -0
- package/src/prompts/tools/learn.md +7 -0
- package/src/prompts/tools/manage-skill.md +9 -0
- package/src/prompts/tools/task.md +3 -0
- package/src/registry/agent-registry.ts +30 -0
- package/src/sdk.ts +88 -24
- package/src/secrets/obfuscator.ts +1 -1
- package/src/session/agent-session.ts +209 -87
- package/src/session/history-storage.ts +2 -2
- package/src/session/indexed-session-storage.ts +7 -17
- package/src/session/session-context.ts +352 -0
- package/src/session/session-entries.ts +194 -0
- package/src/session/session-listing.ts +588 -0
- package/src/session/session-loader.ts +106 -0
- package/src/session/session-manager.ts +933 -3145
- package/src/session/session-migrations.ts +78 -0
- package/src/session/session-paths.ts +193 -0
- package/src/session/session-persistence.ts +131 -0
- package/src/session/session-storage.ts +91 -50
- package/src/session/snapcompact-inline.ts +21 -1
- package/src/session/snapcompact-savings-journal.ts +113 -0
- package/src/session/tool-choice-queue.ts +23 -11
- package/src/slash-commands/builtin-registry.ts +25 -3
- package/src/stt/asr-client.ts +520 -0
- package/src/stt/asr-protocol.ts +65 -0
- package/src/stt/asr-worker.ts +790 -0
- package/src/stt/downloader.ts +107 -47
- package/src/stt/endpointer.ts +259 -0
- package/src/stt/index.ts +5 -1
- package/src/stt/models.ts +150 -0
- package/src/stt/recorder.ts +247 -60
- package/src/stt/stt-controller.ts +201 -22
- package/src/stt/transcriber.ts +37 -68
- package/src/stt/wav.ts +173 -0
- package/src/system-prompt.ts +8 -0
- package/src/task/agents.ts +1 -2
- package/src/task/executor.ts +49 -15
- package/src/task/index.ts +60 -6
- package/src/task/render.ts +83 -8
- package/src/task/types.ts +53 -0
- package/src/tools/ask.ts +8 -0
- package/src/tools/bash.ts +4 -3
- package/src/tools/eval-render.ts +4 -3
- package/src/tools/index.ts +40 -4
- package/src/tools/irc.ts +10 -2
- package/src/tools/job.ts +14 -2
- package/src/tools/learn.ts +144 -0
- package/src/tools/manage-skill.ts +104 -0
- package/src/tools/plan-mode-guard.ts +53 -19
- package/src/tools/renderers.ts +7 -11
- package/src/tools/ssh.ts +4 -3
- package/src/tools/todo.ts +1 -1
- package/src/tools/tts.ts +203 -92
- package/src/tools/write.ts +18 -2
- package/src/tts/downloader.ts +64 -0
- package/src/tts/index.ts +8 -0
- package/src/tts/models.ts +137 -0
- package/src/tts/player.ts +137 -0
- package/src/tts/runtime.ts +21 -0
- package/src/tts/streaming-player.ts +266 -0
- package/src/tts/tts-client.ts +647 -0
- package/src/tts/tts-protocol.ts +60 -0
- package/src/tts/tts-worker.ts +497 -0
- package/src/tts/vocalizer.ts +162 -0
- package/src/tts/wav.ts +58 -0
- package/src/utils/title-generator.ts +48 -5
- package/src/utils/tool-choice.ts +16 -0
- package/src/utils/tools-manager.test.ts +25 -0
- package/src/utils/tools-manager.ts +19 -1
- package/src/web/scrapers/github.ts +96 -0
- package/src/web/search/index.ts +13 -0
- package/src/web/search/providers/searxng.ts +13 -1
- package/dist/types/stt/setup.d.ts +0 -18
- package/src/stt/setup.ts +0 -52
- package/src/stt/transcribe.py +0 -70
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import {
|
|
4
5
|
getPluginsDir,
|
|
@@ -11,6 +12,8 @@ import {
|
|
|
11
12
|
logger,
|
|
12
13
|
} from "@oh-my-pi/pi-utils";
|
|
13
14
|
import { type GitSource, parseGitUrl } from "./git-url";
|
|
15
|
+
import { installLegacyPiSpecifierShim, loadLegacyPiModule } from "./legacy-pi-compat";
|
|
16
|
+
import { resolvePluginManifestEntries } from "./loader";
|
|
14
17
|
import { extractPackageName, parsePluginSpec } from "./parser";
|
|
15
18
|
import type {
|
|
16
19
|
DoctorCheck,
|
|
@@ -74,6 +77,34 @@ function gitInstallSpec(original: string, source: GitSource): string {
|
|
|
74
77
|
return `${source.repo}#${source.ref}`;
|
|
75
78
|
}
|
|
76
79
|
|
|
80
|
+
function findGitPackageName(source: GitSource, deps: Record<string, string>): string | undefined {
|
|
81
|
+
for (const [key, value] of Object.entries(deps)) {
|
|
82
|
+
if (typeof value !== "string") {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const installedSource = parseGitUrl(value);
|
|
86
|
+
if (installedSource && installedSource.host === source.host && installedSource.path === source.path) {
|
|
87
|
+
return key;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function hasDefaultExport(value: unknown): value is { default?: unknown } {
|
|
94
|
+
return typeof value === "object" && value !== null && "default" in value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function hasExtensionFactoryExport(module: unknown): boolean {
|
|
98
|
+
return typeof module === "function" || (hasDefaultExport(module) && typeof module.default === "function");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface PluginPackageSnapshot {
|
|
102
|
+
readonly actualName: string;
|
|
103
|
+
readonly packagePath: string;
|
|
104
|
+
readonly backupRoot: string;
|
|
105
|
+
readonly backupPath: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
77
108
|
// =============================================================================
|
|
78
109
|
// Plugin Manager
|
|
79
110
|
// =============================================================================
|
|
@@ -173,6 +204,88 @@ export class PluginManager {
|
|
|
173
204
|
}
|
|
174
205
|
}
|
|
175
206
|
|
|
207
|
+
async #snapshotInstalledPackage(actualName: string | undefined): Promise<PluginPackageSnapshot | null> {
|
|
208
|
+
if (!actualName) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
const packagePath = path.join(getPluginsNodeModules(), actualName);
|
|
212
|
+
try {
|
|
213
|
+
await fs.promises.lstat(packagePath);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
if (isEnoent(err)) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
throw err;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const backupRoot = await fs.promises.mkdtemp(path.join(os.tmpdir(), "omp-plugin-backup-"));
|
|
222
|
+
const backupPath = path.join(backupRoot, "package");
|
|
223
|
+
await fs.promises.cp(packagePath, backupPath, { recursive: true, verbatimSymlinks: true });
|
|
224
|
+
return { actualName, packagePath, backupRoot, backupPath };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async #cleanupSnapshot(snapshot: PluginPackageSnapshot | null): Promise<void> {
|
|
228
|
+
if (!snapshot) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
await fs.promises.rm(snapshot.backupRoot, { recursive: true, force: true });
|
|
233
|
+
} catch (err) {
|
|
234
|
+
logger.warn("Failed to remove plugin install backup", { plugin: snapshot.actualName, error: String(err) });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async #rollbackFailedInstall(
|
|
239
|
+
actualName: string,
|
|
240
|
+
packageJsonBefore: string,
|
|
241
|
+
snapshot: PluginPackageSnapshot | null,
|
|
242
|
+
): Promise<void> {
|
|
243
|
+
await Bun.write(getPluginsPackageJson(), packageJsonBefore);
|
|
244
|
+
const packagePath = path.join(getPluginsNodeModules(), actualName);
|
|
245
|
+
await fs.promises.rm(packagePath, { recursive: true, force: true });
|
|
246
|
+
if (!snapshot) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
await fs.promises.mkdir(path.dirname(snapshot.packagePath), { recursive: true });
|
|
250
|
+
await fs.promises.cp(snapshot.backupPath, snapshot.packagePath, { recursive: true, verbatimSymlinks: true });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async #validateInstalledExtensions(plugin: InstalledPlugin): Promise<void> {
|
|
254
|
+
const declaredEntries = resolvePluginManifestEntries(plugin, "extensions");
|
|
255
|
+
if (declaredEntries.length === 0) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const errors: string[] = [];
|
|
260
|
+
const loadable: string[] = [];
|
|
261
|
+
for (const { entry, resolvedPath } of declaredEntries) {
|
|
262
|
+
if (resolvedPath === null) {
|
|
263
|
+
errors.push(`${entry}: declared extension entry not found on disk`);
|
|
264
|
+
} else {
|
|
265
|
+
loadable.push(resolvedPath);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (loadable.length > 0) {
|
|
270
|
+
installLegacyPiSpecifierShim();
|
|
271
|
+
for (const extensionPath of loadable) {
|
|
272
|
+
try {
|
|
273
|
+
const module = await loadLegacyPiModule(extensionPath);
|
|
274
|
+
if (!hasExtensionFactoryExport(module)) {
|
|
275
|
+
errors.push(`${extensionPath}: extension does not export a valid factory function`);
|
|
276
|
+
}
|
|
277
|
+
} catch (err) {
|
|
278
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
279
|
+
errors.push(`${extensionPath}: ${message}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (errors.length > 0) {
|
|
285
|
+
throw new Error(`Plugin ${plugin.name} extension validation failed:\n${errors.join("\n")}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
176
289
|
// ==========================================================================
|
|
177
290
|
// Install / Uninstall
|
|
178
291
|
// ==========================================================================
|
|
@@ -217,113 +330,131 @@ export class PluginManager {
|
|
|
217
330
|
};
|
|
218
331
|
}
|
|
219
332
|
const pkgJsonPath = getPluginsPackageJson();
|
|
220
|
-
const
|
|
333
|
+
const packageJsonBefore = await Bun.file(pkgJsonPath).text();
|
|
334
|
+
const depsBefore = await this.#readDeps(pkgJsonPath);
|
|
221
335
|
const packageInstallSpec = gitSource ? gitInstallSpec(spec.packageName, gitSource) : spec.packageName;
|
|
336
|
+
const existingActualName = gitSource
|
|
337
|
+
? findGitPackageName(gitSource, depsBefore)
|
|
338
|
+
: extractPackageName(spec.packageName);
|
|
339
|
+
const packageSnapshot = await this.#snapshotInstalledPackage(existingActualName);
|
|
222
340
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
341
|
+
try {
|
|
342
|
+
// Run npm install
|
|
343
|
+
const proc = Bun.spawn(["bun", "install", packageInstallSpec], {
|
|
344
|
+
cwd: getPluginsDir(),
|
|
345
|
+
stdin: "ignore",
|
|
346
|
+
stdout: "pipe",
|
|
347
|
+
stderr: "pipe",
|
|
348
|
+
windowsHide: true,
|
|
349
|
+
});
|
|
231
350
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
// Resolve actual package name. npm specs encode the name (strip version);
|
|
238
|
-
// git specs do not, so diff plugins/package.json deps to find the new entry.
|
|
239
|
-
let actualName: string;
|
|
240
|
-
if (gitSource) {
|
|
241
|
-
const depsAfter = await this.#readDeps(pkgJsonPath);
|
|
242
|
-
let resolved: string | undefined;
|
|
243
|
-
for (const key of Object.keys(depsAfter)) {
|
|
244
|
-
if (!(key in depsBefore)) {
|
|
245
|
-
resolved = key;
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
351
|
+
const exitCode = await proc.exited;
|
|
352
|
+
if (exitCode !== 0) {
|
|
353
|
+
const stderr = await new Response(proc.stderr).text();
|
|
354
|
+
throw new Error(`npm install failed: ${stderr}`);
|
|
248
355
|
}
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
for (const
|
|
256
|
-
if (
|
|
356
|
+
// Resolve actual package name. npm specs encode the name (strip version);
|
|
357
|
+
// git specs do not, so diff plugins/package.json deps to find the new entry.
|
|
358
|
+
let actualName: string;
|
|
359
|
+
if (gitSource) {
|
|
360
|
+
const depsAfter = await this.#readDeps(pkgJsonPath);
|
|
361
|
+
let resolved: string | undefined;
|
|
362
|
+
for (const key of Object.keys(depsAfter)) {
|
|
363
|
+
if (!(key in depsBefore)) {
|
|
257
364
|
resolved = key;
|
|
258
365
|
break;
|
|
259
366
|
}
|
|
260
367
|
}
|
|
368
|
+
// Fallback: a force-reinstall of an already-present git plugin will not
|
|
369
|
+
// add a new key, just rewrite the existing one to the new spec value.
|
|
370
|
+
// Match by repository identity, not by ref, so failed upgrades from
|
|
371
|
+
// one ref to another still resolve to the original package name.
|
|
372
|
+
if (!resolved) {
|
|
373
|
+
resolved = findGitPackageName(gitSource, depsAfter);
|
|
374
|
+
}
|
|
375
|
+
if (!resolved) {
|
|
376
|
+
throw new Error(
|
|
377
|
+
`Installed ${spec.packageName} but could not determine package name from plugins/package.json`,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
actualName = resolved;
|
|
381
|
+
} else {
|
|
382
|
+
actualName = extractPackageName(spec.packageName);
|
|
261
383
|
}
|
|
262
|
-
|
|
263
|
-
throw new Error(
|
|
264
|
-
`Installed ${spec.packageName} but could not determine package name from plugins/package.json`,
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
actualName = resolved;
|
|
268
|
-
} else {
|
|
269
|
-
actualName = extractPackageName(spec.packageName);
|
|
270
|
-
}
|
|
271
|
-
const pkgPath = path.join(getPluginsNodeModules(), actualName, "package.json");
|
|
384
|
+
const pkgPath = path.join(getPluginsNodeModules(), actualName, "package.json");
|
|
272
385
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
386
|
+
let pkg: { name: string; version: string; omp?: PluginManifest; pi?: PluginManifest };
|
|
387
|
+
try {
|
|
388
|
+
pkg = await Bun.file(pkgPath).json();
|
|
389
|
+
} catch (err) {
|
|
390
|
+
if (isEnoent(err)) {
|
|
391
|
+
throw new Error(`Package installed but package.json not found at ${pkgPath}`);
|
|
392
|
+
}
|
|
393
|
+
throw err;
|
|
279
394
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
);
|
|
395
|
+
const manifest: PluginManifest = pkg.omp || pkg.pi || { version: pkg.version };
|
|
396
|
+
manifest.version = pkg.version;
|
|
397
|
+
|
|
398
|
+
// Resolve enabled features
|
|
399
|
+
let enabledFeatures: string[] | null = null;
|
|
400
|
+
if (spec.features === "*") {
|
|
401
|
+
// All features
|
|
402
|
+
enabledFeatures = manifest.features ? Object.keys(manifest.features) : null;
|
|
403
|
+
} else if (Array.isArray(spec.features)) {
|
|
404
|
+
if (spec.features.length > 0) {
|
|
405
|
+
// Validate requested features exist
|
|
406
|
+
if (manifest.features) {
|
|
407
|
+
for (const feat of spec.features) {
|
|
408
|
+
if (!(feat in manifest.features)) {
|
|
409
|
+
throw new Error(
|
|
410
|
+
`Unknown feature "${feat}" in ${actualName}. Available: ${Object.keys(manifest.features).join(", ")}`,
|
|
411
|
+
);
|
|
412
|
+
}
|
|
299
413
|
}
|
|
300
414
|
}
|
|
415
|
+
enabledFeatures = spec.features;
|
|
416
|
+
} else {
|
|
417
|
+
// Empty array = no optional features
|
|
418
|
+
enabledFeatures = [];
|
|
301
419
|
}
|
|
302
|
-
enabledFeatures = spec.features;
|
|
303
|
-
} else {
|
|
304
|
-
// Empty array = no optional features
|
|
305
|
-
enabledFeatures = [];
|
|
306
420
|
}
|
|
307
|
-
|
|
308
|
-
// null = use defaults
|
|
421
|
+
// null = use defaults
|
|
309
422
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
423
|
+
const installedPlugin: InstalledPlugin = {
|
|
424
|
+
name: pkg.name,
|
|
425
|
+
version: pkg.version,
|
|
426
|
+
path: path.join(getPluginsNodeModules(), actualName),
|
|
427
|
+
manifest,
|
|
428
|
+
enabledFeatures,
|
|
429
|
+
enabled: true,
|
|
430
|
+
};
|
|
318
431
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
432
|
+
try {
|
|
433
|
+
await this.#validateInstalledExtensions(installedPlugin);
|
|
434
|
+
} catch (err) {
|
|
435
|
+
try {
|
|
436
|
+
await this.#rollbackFailedInstall(actualName, packageJsonBefore, packageSnapshot);
|
|
437
|
+
} catch (rollbackErr) {
|
|
438
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
439
|
+
const rollbackMessage = rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr);
|
|
440
|
+
throw new Error(`${message}\nRollback failed: ${rollbackMessage}`);
|
|
441
|
+
}
|
|
442
|
+
throw err;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Update runtime config
|
|
446
|
+
const config = await this.#ensureConfigLoaded();
|
|
447
|
+
config.plugins[pkg.name] = {
|
|
448
|
+
version: pkg.version,
|
|
449
|
+
enabledFeatures,
|
|
450
|
+
enabled: true,
|
|
451
|
+
};
|
|
452
|
+
await this.#saveRuntimeConfig();
|
|
453
|
+
|
|
454
|
+
return installedPlugin;
|
|
455
|
+
} finally {
|
|
456
|
+
await this.#cleanupSnapshot(packageSnapshot);
|
|
457
|
+
}
|
|
327
458
|
}
|
|
328
459
|
|
|
329
460
|
/**
|
|
@@ -17,7 +17,7 @@ import type { CompactionPreparation, CompactionResult } from "@oh-my-pi/pi-agent
|
|
|
17
17
|
import type { ImageContent, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
|
|
18
18
|
import type { Rule } from "../capability/rule";
|
|
19
19
|
import type { Goal, GoalModeState } from "../goals/state";
|
|
20
|
-
import type { BranchSummaryEntry, CompactionEntry, SessionEntry } from "../session/session-
|
|
20
|
+
import type { BranchSummaryEntry, CompactionEntry, SessionEntry } from "../session/session-entries";
|
|
21
21
|
import type { TodoItem } from "../tools/todo";
|
|
22
22
|
|
|
23
23
|
// ============================================================================
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import { getProjectDir } from "@oh-my-pi/pi-utils";
|
|
4
|
+
import {
|
|
5
|
+
isValidManagedSkillName,
|
|
6
|
+
MANAGED_SKILLS_PROVIDER_ID,
|
|
7
|
+
sanitizeManagedDescription,
|
|
8
|
+
} from "../autolearn/managed-skills";
|
|
4
9
|
import { skillCapability } from "../capability/skill";
|
|
5
10
|
import type { SourceMeta } from "../capability/types";
|
|
6
11
|
import type { SkillsSettings } from "../config/settings";
|
|
@@ -54,6 +59,21 @@ export function resetActiveSkillsForTests(): void {
|
|
|
54
59
|
activeSkills = [];
|
|
55
60
|
}
|
|
56
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Whether `name` is already claimed by an active authored (non-managed) skill.
|
|
64
|
+
*
|
|
65
|
+
* Managed (auto-learn) skills resolve dead-last in discovery, so an authored
|
|
66
|
+
* skill of the same name always wins (see `loadSkills`) and a managed skill
|
|
67
|
+
* written under an authored name is silently dropped — it never surfaces.
|
|
68
|
+
* `manage_skill` create consults this to refuse the write up front instead of
|
|
69
|
+
* reporting a false "Created" for a skill that can never appear.
|
|
70
|
+
*/
|
|
71
|
+
export function isNameClaimedByAuthoredSkill(name: string): boolean {
|
|
72
|
+
return getActiveSkills().some(
|
|
73
|
+
skill => skill.name === name && skill._source?.provider !== MANAGED_SKILLS_PROVIDER_ID,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
57
77
|
export interface LoadSkillsFromDirOptions {
|
|
58
78
|
/** Directory to scan for skills */
|
|
59
79
|
dir: string;
|
|
@@ -119,24 +139,23 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
119
139
|
if (!enabled) {
|
|
120
140
|
return { skills: [], warnings: [] };
|
|
121
141
|
}
|
|
122
|
-
|
|
123
142
|
// Fall-through gate for third-party CLI providers (claude-plugins, opencode,
|
|
124
|
-
// gemini, github, ...) that share user intent with the named
|
|
125
|
-
// but don't have a dedicated control of their own.
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
enableClaudeProject ||
|
|
133
|
-
enablePiUser ||
|
|
134
|
-
enablePiProject ||
|
|
135
|
-
enableAgentsUser ||
|
|
136
|
-
enableAgentsProject;
|
|
143
|
+
// gemini, github, ...) that share user intent with the named third-party
|
|
144
|
+
// source toggles but don't have a dedicated control of their own. Only the
|
|
145
|
+
// third-party toggles count here: the OMP-native providers (`agents`,
|
|
146
|
+
// `native`) get explicit branches in `isSourceEnabled` below, so folding
|
|
147
|
+
// them into the fallback would re-enable unrelated third-party CLIs whenever
|
|
148
|
+
// the user kept the default `.agent[s]/skills` toggles on while turning off
|
|
149
|
+
// Codex/Claude/Pi (issue #2401 / PR #2405 review).
|
|
150
|
+
const anyThirdPartySkillToggleEnabled =
|
|
151
|
+
enableCodexUser || enableClaudeUser || enableClaudeProject || enablePiUser || enablePiProject;
|
|
137
152
|
|
|
138
153
|
function isSourceEnabled(source: SourceMeta): boolean {
|
|
139
154
|
const { provider, level } = source;
|
|
155
|
+
// Managed skills (auto-learn) are OMP-native and discovered unconditionally
|
|
156
|
+
// — third-party CLI toggles must never silently hide them (cf. #2401). The
|
|
157
|
+
// master `enabled` flag above still gates them.
|
|
158
|
+
if (provider === MANAGED_SKILLS_PROVIDER_ID) return true;
|
|
140
159
|
if (provider === "codex" && level === "user") return enableCodexUser;
|
|
141
160
|
if (provider === "claude" && level === "user") return enableClaudeUser;
|
|
142
161
|
if (provider === "claude" && level === "project") return enableClaudeProject;
|
|
@@ -144,7 +163,7 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
144
163
|
if (provider === "native" && level === "project") return enablePiProject;
|
|
145
164
|
if (provider === "agents" && level === "user") return enableAgentsUser;
|
|
146
165
|
if (provider === "agents" && level === "project") return enableAgentsProject;
|
|
147
|
-
return
|
|
166
|
+
return anyThirdPartySkillToggleEnabled;
|
|
148
167
|
}
|
|
149
168
|
|
|
150
169
|
// Use capability API to load all skills
|
|
@@ -192,6 +211,9 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
192
211
|
// Process skills with resolved paths
|
|
193
212
|
for (let i = 0; i < filteredSkills.length; i++) {
|
|
194
213
|
const capSkill = filteredSkills[i];
|
|
214
|
+
// Managed (auto-learn) skills are resolved dead-last (below) so any
|
|
215
|
+
// authored skill of the same name — from ANY provider or custom dir — wins.
|
|
216
|
+
if (capSkill._source.provider === MANAGED_SKILLS_PROVIDER_ID) continue;
|
|
195
217
|
const resolvedPath = realPaths[i];
|
|
196
218
|
|
|
197
219
|
// Skip silently if we've already loaded this exact file (via symlink)
|
|
@@ -285,6 +307,65 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
285
307
|
}
|
|
286
308
|
}
|
|
287
309
|
|
|
310
|
+
// Managed (auto-learn) skills resolve dead-last with first-wins. Source from
|
|
311
|
+
// result.all (pre-dedup): capability-level dedup runs BEFORE isSourceEnabled,
|
|
312
|
+
// so a managed skill can be shadowed by a higher-priority authored skill that
|
|
313
|
+
// is itself disabled here — managed must stay visible regardless of toggles.
|
|
314
|
+
// Validate the on-disk name (a hand-placed managed file could carry an unsafe
|
|
315
|
+
// frontmatter name) and re-sanitize the description on read. Descriptions and
|
|
316
|
+
// names both render unescaped into the system prompt.
|
|
317
|
+
const managedCandidates = result.all.filter(
|
|
318
|
+
capSkill =>
|
|
319
|
+
capSkill._source.provider === MANAGED_SKILLS_PROVIDER_ID &&
|
|
320
|
+
isValidManagedSkillName(capSkill.name) &&
|
|
321
|
+
!disabledSkillNames.has(capSkill.name) &&
|
|
322
|
+
!matchesIgnorePatterns(capSkill.name) &&
|
|
323
|
+
matchesIncludePatterns(capSkill.name),
|
|
324
|
+
);
|
|
325
|
+
// Names claimed by any ENABLED authored skill (from the pre-dedup superset).
|
|
326
|
+
// Managed defers to these even when capability dedup hid an enabled authored
|
|
327
|
+
// skill behind a disabled higher-priority one, so managed never masks it.
|
|
328
|
+
const enabledAuthoredNames = new Set(
|
|
329
|
+
result.all
|
|
330
|
+
.filter(
|
|
331
|
+
capSkill => capSkill._source.provider !== MANAGED_SKILLS_PROVIDER_ID && isSourceEnabled(capSkill._source),
|
|
332
|
+
)
|
|
333
|
+
.map(capSkill => capSkill.name),
|
|
334
|
+
);
|
|
335
|
+
const managedRealPaths = await Promise.all(
|
|
336
|
+
managedCandidates.map(async capSkill => {
|
|
337
|
+
try {
|
|
338
|
+
return await fs.realpath(capSkill.path);
|
|
339
|
+
} catch {
|
|
340
|
+
return capSkill.path;
|
|
341
|
+
}
|
|
342
|
+
}),
|
|
343
|
+
);
|
|
344
|
+
for (let i = 0; i < managedCandidates.length; i++) {
|
|
345
|
+
const capSkill = managedCandidates[i];
|
|
346
|
+
const resolvedPath = managedRealPaths[i];
|
|
347
|
+
if (realPathSet.has(resolvedPath)) continue;
|
|
348
|
+
if (enabledAuthoredNames.has(capSkill.name)) continue; // an enabled authored skill owns this name
|
|
349
|
+
// Already claimed — e.g. by a custom-directory skill. LOAD-BEARING: custom
|
|
350
|
+
// dirs never enter `result.all`, so they are absent from `enabledAuthoredNames`
|
|
351
|
+
// above; this map check is the ONLY veto that lets a custom-dir authored skill
|
|
352
|
+
// win over a same-named managed one. The custom-dir loop (which populates
|
|
353
|
+
// skillMap, ~30 lines up) MUST run before this block — do not reorder.
|
|
354
|
+
if (skillMap.has(capSkill.name)) continue;
|
|
355
|
+
const rawDescription =
|
|
356
|
+
typeof capSkill.frontmatter?.description === "string" ? capSkill.frontmatter.description : "";
|
|
357
|
+
skillMap.set(capSkill.name, {
|
|
358
|
+
name: capSkill.name,
|
|
359
|
+
description: sanitizeManagedDescription(rawDescription),
|
|
360
|
+
filePath: capSkill.path,
|
|
361
|
+
baseDir: capSkill.path.replace(/[\\/]SKILL\.md$/, ""),
|
|
362
|
+
source: `${capSkill._source.provider}:${capSkill.level}`,
|
|
363
|
+
hide: capSkill.frontmatter?.hide === true || capSkill.frontmatter?.disableModelInvocation === true,
|
|
364
|
+
_source: capSkill._source,
|
|
365
|
+
});
|
|
366
|
+
realPathSet.add(resolvedPath);
|
|
367
|
+
}
|
|
368
|
+
|
|
288
369
|
const skills = Array.from(skillMap.values());
|
|
289
370
|
// Deterministic ordering for prompt stability (case-insensitive, then exact name, then path).
|
|
290
371
|
skills.sort((a, b) => compareSkillOrder(a.name, a.filePath, b.name, b.filePath));
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { instrumentedCompleteSimple, resolveTelemetry } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import type { Tool } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import { prompt } from "@oh-my-pi/pi-utils";
|
|
4
|
+
import { extractTextContent, extractToolCall, parseJsonPayload } from "../commit/utils";
|
|
5
|
+
import guidedGoalInterviewPrompt from "../prompts/goals/guided-goal-interview.md" with { type: "text" };
|
|
6
|
+
import guidedGoalSystemPrompt from "../prompts/goals/guided-goal-system.md" with { type: "text" };
|
|
7
|
+
import type { AgentSession } from "../session/agent-session";
|
|
8
|
+
import { toReasoningEffort } from "../thinking";
|
|
9
|
+
|
|
10
|
+
const RESPOND_TOOL_NAME = "respond";
|
|
11
|
+
|
|
12
|
+
const RESPOND_TOOL: Tool = {
|
|
13
|
+
name: RESPOND_TOOL_NAME,
|
|
14
|
+
description: "Return the next guided-goal interview step.",
|
|
15
|
+
parameters: {
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
kind: { type: "string", enum: ["question", "ready"] },
|
|
19
|
+
question: { type: "string" },
|
|
20
|
+
objective: { type: "string" },
|
|
21
|
+
},
|
|
22
|
+
required: ["kind"],
|
|
23
|
+
additionalProperties: false,
|
|
24
|
+
},
|
|
25
|
+
strict: false,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export interface GuidedGoalMessage {
|
|
29
|
+
role: "user" | "assistant";
|
|
30
|
+
content: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type GuidedGoalTurnResult =
|
|
34
|
+
| { kind: "question"; question: string; objective?: string }
|
|
35
|
+
| { kind: "ready"; objective: string };
|
|
36
|
+
|
|
37
|
+
export interface GuidedGoalTurnOptions {
|
|
38
|
+
messages: readonly GuidedGoalMessage[];
|
|
39
|
+
signal?: AbortSignal;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseGuidedGoalPayload(value: unknown): GuidedGoalTurnResult {
|
|
43
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
44
|
+
throw new Error("guided goal returned an invalid response");
|
|
45
|
+
}
|
|
46
|
+
const payload = value as Record<string, unknown>;
|
|
47
|
+
if (payload.kind === "question" && typeof payload.question === "string" && payload.question.trim()) {
|
|
48
|
+
const question = payload.question.trim();
|
|
49
|
+
if (typeof payload.objective === "string" && payload.objective.trim()) {
|
|
50
|
+
return { kind: "question", question, objective: payload.objective.trim() };
|
|
51
|
+
}
|
|
52
|
+
return { kind: "question", question };
|
|
53
|
+
}
|
|
54
|
+
if (payload.kind === "ready" && typeof payload.objective === "string" && payload.objective.trim()) {
|
|
55
|
+
return { kind: "ready", objective: payload.objective.trim() };
|
|
56
|
+
}
|
|
57
|
+
throw new Error("guided goal returned an invalid response");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseToolArguments(value: unknown): unknown {
|
|
61
|
+
return typeof value === "string" ? parseJsonPayload(value) : value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function runGuidedGoalTurn(
|
|
65
|
+
session: AgentSession,
|
|
66
|
+
options: GuidedGoalTurnOptions,
|
|
67
|
+
): Promise<GuidedGoalTurnResult> {
|
|
68
|
+
const plan = session.resolveRoleModelWithThinking("plan");
|
|
69
|
+
const resolved = plan.model ? plan : session.resolveRoleModelWithThinking("slow");
|
|
70
|
+
if (!resolved.model) {
|
|
71
|
+
throw new Error("No plan or slow model is available for /guided-goal.");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const apiKey = await session.modelRegistry.getApiKey(resolved.model, session.sessionId);
|
|
75
|
+
if (!apiKey) {
|
|
76
|
+
throw new Error(`No API key for ${resolved.model.provider}/${resolved.model.id}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const userPrompt = prompt.render(guidedGoalInterviewPrompt, {
|
|
80
|
+
messages: options.messages.map(message => ({ label: message.role.toUpperCase(), content: message.content })),
|
|
81
|
+
});
|
|
82
|
+
// Secret obfuscation: route the user-authored transcript through the session obfuscator the
|
|
83
|
+
// same way normal turns do, so an API key / secret typed into the rough goal or an answer is
|
|
84
|
+
// never sent verbatim to the plan/slow provider. Deobfuscated again below before display/use.
|
|
85
|
+
const obfuscator = session.obfuscator;
|
|
86
|
+
const promptText = obfuscator?.hasSecrets() ? obfuscator.obfuscate(userPrompt) : userPrompt;
|
|
87
|
+
const response = await instrumentedCompleteSimple(
|
|
88
|
+
resolved.model,
|
|
89
|
+
{
|
|
90
|
+
systemPrompt: [prompt.render(guidedGoalSystemPrompt)],
|
|
91
|
+
messages: [{ role: "user", content: [{ type: "text", text: promptText }], timestamp: Date.now() }],
|
|
92
|
+
tools: [RESPOND_TOOL],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
apiKey: session.modelRegistry.resolver(resolved.model, session.sessionId),
|
|
96
|
+
signal: options.signal,
|
|
97
|
+
reasoning: toReasoningEffort(resolved.thinkingLevel),
|
|
98
|
+
toolChoice: { type: "tool", name: RESPOND_TOOL_NAME },
|
|
99
|
+
},
|
|
100
|
+
{ telemetry: resolveTelemetry(session.agent.telemetry, session.sessionId), oneshotKind: "guided_goal_setup" },
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (response.stopReason === "error") {
|
|
104
|
+
throw new Error(response.errorMessage ?? "guided goal request failed");
|
|
105
|
+
}
|
|
106
|
+
if (response.stopReason === "aborted") {
|
|
107
|
+
throw new Error("guided goal request aborted");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const call = extractToolCall(response, RESPOND_TOOL_NAME);
|
|
111
|
+
let result: GuidedGoalTurnResult;
|
|
112
|
+
if (call) {
|
|
113
|
+
result = parseGuidedGoalPayload(parseToolArguments(call.arguments));
|
|
114
|
+
} else {
|
|
115
|
+
const text = extractTextContent(response);
|
|
116
|
+
if (!text) {
|
|
117
|
+
throw new Error("guided goal returned an invalid response");
|
|
118
|
+
}
|
|
119
|
+
result = parseGuidedGoalPayload(parseJsonPayload(text));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Reverse the obfuscation: restore any secret placeholders the model echoed back before the
|
|
123
|
+
// question/objective is shown or the goal is started.
|
|
124
|
+
if (!obfuscator?.hasSecrets()) return result;
|
|
125
|
+
if (result.kind === "question") {
|
|
126
|
+
return {
|
|
127
|
+
kind: "question",
|
|
128
|
+
question: obfuscator.deobfuscate(result.question),
|
|
129
|
+
objective: result.objective !== undefined ? obfuscator.deobfuscate(result.objective) : undefined,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return { kind: "ready", objective: obfuscator.deobfuscate(result.objective) };
|
|
133
|
+
}
|
package/src/goals/state.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
11
|
-
import type { SessionEntry } from "../session/session-
|
|
11
|
+
import type { SessionEntry } from "../session/session-entries";
|
|
12
12
|
import type { HindsightMessage } from "./content";
|
|
13
13
|
|
|
14
14
|
export interface ReadonlySessionManagerLike {
|