@oh-my-pi/pi-coding-agent 15.9.0 → 15.9.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 +67 -0
- package/dist/types/cli/dry-balance-cli.d.ts +104 -0
- package/dist/types/cli/update-cli.d.ts +15 -1
- package/dist/types/commands/dry-balance.d.ts +31 -0
- package/dist/types/config/append-only-context-mode.d.ts +8 -0
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/models-config-schema.d.ts +18 -0
- package/dist/types/config/settings-schema.d.ts +3 -3
- package/dist/types/config/settings.d.ts +11 -0
- package/dist/types/discovery/helpers.d.ts +1 -0
- package/dist/types/exa/mcp-client.d.ts +2 -1
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -3
- package/dist/types/hindsight/bank.d.ts +17 -9
- package/dist/types/hindsight/mental-models.d.ts +1 -1
- package/dist/types/hindsight/state.d.ts +9 -3
- package/dist/types/mcp/json-rpc.d.ts +6 -1
- package/dist/types/mcp/manager.d.ts +1 -1
- package/dist/types/mcp/tool-bridge.d.ts +4 -0
- package/dist/types/mnemopi/state.d.ts +2 -2
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -0
- package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -0
- package/dist/types/modes/components/plugin-settings.d.ts +40 -8
- package/dist/types/modes/components/session-selector.d.ts +8 -3
- package/dist/types/modes/components/settings-selector.d.ts +1 -1
- package/dist/types/modes/components/transcript-container.d.ts +3 -2
- package/dist/types/modes/utils/keybinding-matchers.d.ts +4 -0
- package/dist/types/session/agent-session.d.ts +13 -1
- package/dist/types/session/auth-storage.d.ts +2 -2
- package/dist/types/session/history-storage.d.ts +3 -4
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/session-manager.d.ts +1 -0
- package/dist/types/slash-commands/types.d.ts +17 -4
- package/dist/types/task/types.d.ts +2 -0
- package/dist/types/tiny/text.d.ts +17 -0
- package/dist/types/tools/index.d.ts +16 -0
- package/dist/types/tools/path-utils.d.ts +11 -0
- package/dist/types/web/search/providers/base.d.ts +14 -0
- package/dist/types/web/search/providers/exa.d.ts +9 -0
- package/dist/types/web/search/providers/perplexity.d.ts +2 -2
- package/dist/types/web/search/types.d.ts +2 -1
- package/package.json +9 -9
- package/src/cli/dry-balance-cli.ts +823 -0
- package/src/cli/session-picker.ts +1 -0
- package/src/cli/update-cli.ts +54 -2
- package/src/cli-commands.ts +1 -0
- package/src/commands/completions.ts +1 -1
- package/src/commands/dry-balance.ts +43 -0
- package/src/config/append-only-context-mode.ts +37 -0
- package/src/config/model-registry.ts +6 -0
- package/src/config/models-config-schema.ts +3 -0
- package/src/config/settings-schema.ts +2 -2
- package/src/config/settings.ts +38 -0
- package/src/discovery/builtin-rules/ts-no-tiny-functions.md +1 -0
- package/src/discovery/github.ts +37 -1
- package/src/discovery/helpers.ts +3 -1
- package/src/exa/mcp-client.ts +11 -5
- package/src/extensibility/plugins/legacy-pi-compat.ts +245 -25
- package/src/hindsight/backend.ts +184 -35
- package/src/hindsight/bank.ts +32 -22
- package/src/hindsight/mental-models.ts +1 -1
- package/src/hindsight/state.ts +21 -7
- package/src/internal-urls/docs-index.generated.ts +5 -5
- package/src/internal-urls/omp-protocol.ts +8 -2
- package/src/main.ts +4 -2
- package/src/mcp/json-rpc.ts +8 -0
- package/src/mcp/manager.ts +40 -21
- package/src/mcp/render.ts +3 -0
- package/src/mcp/tool-bridge.ts +10 -2
- package/src/mcp/transports/http.ts +33 -16
- package/src/mnemopi/state.ts +4 -4
- package/src/modes/acp/acp-agent.ts +168 -3
- package/src/modes/components/agent-dashboard.ts +103 -31
- package/src/modes/components/extensions/extension-dashboard.ts +56 -10
- package/src/modes/components/history-search.ts +128 -14
- package/src/modes/components/plugin-settings.ts +270 -36
- package/src/modes/components/session-selector.ts +45 -14
- package/src/modes/components/settings-selector.ts +1 -1
- package/src/modes/components/tips.txt +5 -1
- package/src/modes/components/transcript-container.ts +35 -6
- package/src/modes/components/tree-selector.ts +29 -2
- package/src/modes/controllers/command-controller.ts +4 -3
- package/src/modes/controllers/input-controller.ts +18 -7
- package/src/modes/controllers/selector-controller.ts +30 -19
- package/src/modes/interactive-mode.ts +38 -3
- package/src/modes/setup-wizard/scenes/sign-in.ts +27 -7
- package/src/modes/utils/keybinding-matchers.ts +10 -0
- package/src/prompts/agents/explore.md +1 -0
- package/src/prompts/agents/librarian.md +1 -0
- package/src/prompts/dry-balance-bench.md +8 -0
- package/src/prompts/steering/user-interjection.md +10 -0
- package/src/prompts/system/agent-creation-architect.md +1 -26
- package/src/prompts/system/system-prompt.md +143 -145
- package/src/prompts/system/title-system.md +3 -2
- package/src/prompts/tools/browser.md +29 -29
- package/src/prompts/tools/render-mermaid.md +2 -2
- package/src/sdk.ts +87 -30
- package/src/session/agent-session.ts +96 -14
- package/src/session/auth-storage.ts +4 -0
- package/src/session/history-storage.ts +11 -18
- package/src/session/messages.ts +80 -0
- package/src/session/session-manager.ts +7 -1
- package/src/slash-commands/types.ts +27 -10
- package/src/task/executor.ts +6 -2
- package/src/task/index.ts +8 -7
- package/src/task/types.ts +2 -0
- package/src/tiny/text.ts +112 -1
- package/src/tools/bash.ts +3 -4
- package/src/tools/index.ts +16 -0
- package/src/tools/job.ts +3 -3
- package/src/tools/memory-recall.ts +1 -1
- package/src/tools/memory-reflect.ts +3 -3
- package/src/tools/path-utils.ts +21 -0
- package/src/tools/search.ts +18 -1
- package/src/tools/ssh.ts +26 -10
- package/src/tools/write.ts +14 -2
- package/src/tui/status-line.ts +15 -4
- package/src/utils/file-mentions.ts +7 -107
- package/src/utils/title-generator.ts +66 -38
- package/src/web/search/index.ts +3 -1
- package/src/web/search/provider.ts +1 -1
- package/src/web/search/providers/base.ts +17 -0
- package/src/web/search/providers/exa.ts +111 -7
- package/src/web/search/providers/perplexity.ts +8 -4
- package/src/web/search/types.ts +2 -1
package/src/cli/update-cli.ts
CHANGED
|
@@ -27,6 +27,33 @@ const PACKAGE = "@oh-my-pi/pi-coding-agent";
|
|
|
27
27
|
*/
|
|
28
28
|
const NPM_REGISTRY = "https://registry.npmjs.org/";
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Core native addon package. Bumped in lock-step with {@link PACKAGE} so the
|
|
32
|
+
* version sentinel the loader looks up at runtime matches the `.node` on
|
|
33
|
+
* disk; see {@link buildBunInstallArgs} for why this must be installed
|
|
34
|
+
* explicitly rather than inherited as a transitive dependency.
|
|
35
|
+
*/
|
|
36
|
+
const NATIVES_PACKAGE = "@oh-my-pi/pi-natives";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Platform tags the release pipeline publishes as
|
|
40
|
+
* `@oh-my-pi/pi-natives-<tag>` leaves. Mirrors `SUPPORTED_PLATFORMS` in
|
|
41
|
+
* `packages/natives/native/loader-state.js` and `LEAF_TARGETS` in
|
|
42
|
+
* `packages/natives/scripts/gen-npm-packages.ts`; kept here as the local
|
|
43
|
+
* source of truth so the update path stays free of cross-package imports.
|
|
44
|
+
*/
|
|
45
|
+
const SUPPORTED_NATIVE_TAGS: ReadonlySet<string> = new Set([
|
|
46
|
+
"linux-x64",
|
|
47
|
+
"linux-arm64",
|
|
48
|
+
"darwin-x64",
|
|
49
|
+
"darwin-arm64",
|
|
50
|
+
"win32-x64",
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
function currentNativeTag(): string {
|
|
54
|
+
return `${process.platform}-${process.arch}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
30
57
|
interface ReleaseInfo {
|
|
31
58
|
tag: string;
|
|
32
59
|
version: string;
|
|
@@ -319,9 +346,34 @@ export async function replaceBinaryForUpdate(options: BinaryReplacementOptions):
|
|
|
319
346
|
*
|
|
320
347
|
* Together these two flags make `omp update` produce exactly the registry
|
|
321
348
|
* lookup the version check just performed. See #1686.
|
|
349
|
+
*
|
|
350
|
+
* Also pins {@link NATIVES_PACKAGE} and the platform-specific
|
|
351
|
+
* `@oh-my-pi/pi-natives-<tag>` leaf to `expectedVersion`. `bun install -g`
|
|
352
|
+
* does not reliably refresh transitive `optionalDependencies` when the
|
|
353
|
+
* top-level package is the only one bumped, so the native addon and its
|
|
354
|
+
* version sentinel can drift out of sync with the freshly installed
|
|
355
|
+
* `@oh-my-pi/pi-coding-agent` and the loader aborts at
|
|
356
|
+
* `validateLoadedBindings` on the next launch
|
|
357
|
+
* (`The .node file on disk is from a different release than this loader`).
|
|
358
|
+
* Listing the natives explicitly forces bun to replace them in lock-step.
|
|
359
|
+
* The leaf is added only on tags the release pipeline actually publishes
|
|
360
|
+
* ({@link SUPPORTED_NATIVE_TAGS}) so unsupported platforms still fail with
|
|
361
|
+
* the original "no matching version" message instead of `EBADPLATFORM`.
|
|
362
|
+
* See #1824.
|
|
322
363
|
*/
|
|
323
|
-
export function buildBunInstallArgs(expectedVersion: string): string[] {
|
|
324
|
-
|
|
364
|
+
export function buildBunInstallArgs(expectedVersion: string, nativeTag: string = currentNativeTag()): string[] {
|
|
365
|
+
const args = [
|
|
366
|
+
"install",
|
|
367
|
+
"-g",
|
|
368
|
+
"--no-cache",
|
|
369
|
+
`--registry=${NPM_REGISTRY}`,
|
|
370
|
+
`${PACKAGE}@${expectedVersion}`,
|
|
371
|
+
`${NATIVES_PACKAGE}@${expectedVersion}`,
|
|
372
|
+
];
|
|
373
|
+
if (SUPPORTED_NATIVE_TAGS.has(nativeTag)) {
|
|
374
|
+
args.push(`${NATIVES_PACKAGE}-${nativeTag}@${expectedVersion}`);
|
|
375
|
+
}
|
|
376
|
+
return args;
|
|
325
377
|
}
|
|
326
378
|
|
|
327
379
|
/**
|
package/src/cli-commands.ts
CHANGED
|
@@ -20,6 +20,7 @@ export const commands: CommandEntry[] = [
|
|
|
20
20
|
{ name: "completions", load: () => import("./commands/completions").then(m => m.default) },
|
|
21
21
|
{ name: "__complete", load: () => import("./commands/complete").then(m => m.default) },
|
|
22
22
|
{ name: "config", load: () => import("./commands/config").then(m => m.default) },
|
|
23
|
+
{ name: "dry-balance", load: () => import("./commands/dry-balance").then(m => m.default) },
|
|
23
24
|
{ name: "grep", load: () => import("./commands/grep").then(m => m.default) },
|
|
24
25
|
{ name: "grievances", load: () => import("./commands/grievances").then(m => m.default) },
|
|
25
26
|
{ name: "install", load: () => import("./commands/install").then(m => m.default) },
|
|
@@ -51,7 +51,7 @@ export default class Completions extends Command {
|
|
|
51
51
|
|
|
52
52
|
const config: CliConfig = { bin: APP_NAME, version: VERSION, commands: map };
|
|
53
53
|
const spec = buildSpec(config, ROOT_COMMAND, aliasMap);
|
|
54
|
-
|
|
54
|
+
await Bun.write(Bun.stdout, generateCompletion(shell, spec));
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
2
|
+
import { runDryBalanceCommand } from "../cli/dry-balance-cli";
|
|
3
|
+
|
|
4
|
+
export default class DryBalance extends Command {
|
|
5
|
+
static description = "Dry-run OAuth account balancing across random session ids";
|
|
6
|
+
|
|
7
|
+
static args = {
|
|
8
|
+
model: Args.string({
|
|
9
|
+
description: "Model selector (provider/model or fuzzy id). Defaults to the configured default model.",
|
|
10
|
+
required: false,
|
|
11
|
+
}),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
static flags = {
|
|
15
|
+
model: Flags.string({ description: "Model selector (same syntax as --model on omp)" }),
|
|
16
|
+
count: Flags.integer({ description: "Number of random session ids to try", default: 100 }),
|
|
17
|
+
concurrency: Flags.integer({ description: "Maximum concurrent credential resolutions", default: 32 }),
|
|
18
|
+
json: Flags.boolean({ description: "Output JSON" }),
|
|
19
|
+
bench: Flags.boolean({ description: "Send one live benchmark request per OAuth account" }),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
static examples = [
|
|
23
|
+
"# Dry-run the configured default model with 100 random session ids\n omp dry-balance",
|
|
24
|
+
"# Dry-run a specific model\n omp dry-balance anthropic/claude-sonnet-4-5",
|
|
25
|
+
"# Larger run with bounded concurrency\n omp dry-balance --model openai-codex/gpt-5-codex --count 1000 --concurrency 64",
|
|
26
|
+
"# Benchmark every OAuth account in parallel\n omp dry-balance --bench",
|
|
27
|
+
"# Machine-readable output\n omp dry-balance --json",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
async run(): Promise<void> {
|
|
31
|
+
const { args, flags } = await this.parse(DryBalance);
|
|
32
|
+
await runDryBalanceCommand({
|
|
33
|
+
model: args.model,
|
|
34
|
+
flags: {
|
|
35
|
+
model: flags.model,
|
|
36
|
+
count: flags.count,
|
|
37
|
+
concurrency: flags.concurrency,
|
|
38
|
+
json: flags.json,
|
|
39
|
+
bench: flags.bench,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Provider metadata needed to resolve append-only context mode. */
|
|
2
|
+
export interface AppendOnlyContextModel {
|
|
3
|
+
provider: string;
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
compat?: object;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function isXiaomiHost(baseUrl: string): boolean {
|
|
9
|
+
try {
|
|
10
|
+
const host = new URL(baseUrl).hostname;
|
|
11
|
+
return host === "xiaomimimo.com" || host.endsWith(".xiaomimimo.com");
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function shouldAutoEnableAppendOnlyContext(model: AppendOnlyContextModel | null | undefined): boolean {
|
|
18
|
+
if (!model) return false;
|
|
19
|
+
if (model.provider === "deepseek") return true;
|
|
20
|
+
if (isXiaomiHost(model.baseUrl)) return true;
|
|
21
|
+
return !!model.compat && "supportsStore" in model.compat && model.compat.supportsStore === true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Resolves whether append-only context should be active for a model and setting. */
|
|
25
|
+
export function shouldEnableAppendOnlyContext(
|
|
26
|
+
setting: "auto" | "on" | "off" | undefined,
|
|
27
|
+
model: AppendOnlyContextModel | null | undefined,
|
|
28
|
+
): boolean {
|
|
29
|
+
switch (setting ?? "auto") {
|
|
30
|
+
case "on":
|
|
31
|
+
return true;
|
|
32
|
+
case "off":
|
|
33
|
+
return false;
|
|
34
|
+
default:
|
|
35
|
+
return shouldAutoEnableAppendOnlyContext(model);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -547,6 +547,7 @@ function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<A
|
|
|
547
547
|
if (override.input !== undefined) result.input = override.input as ("text" | "image")[];
|
|
548
548
|
if (override.contextWindow !== undefined) result.contextWindow = override.contextWindow;
|
|
549
549
|
if (override.maxTokens !== undefined) result.maxTokens = override.maxTokens;
|
|
550
|
+
if (override.omitMaxOutputTokens !== undefined) result.omitMaxOutputTokens = override.omitMaxOutputTokens;
|
|
550
551
|
if (override.contextPromotionTarget !== undefined) result.contextPromotionTarget = override.contextPromotionTarget;
|
|
551
552
|
if (override.premiumMultiplier !== undefined) result.premiumMultiplier = override.premiumMultiplier;
|
|
552
553
|
if (override.cost) {
|
|
@@ -575,6 +576,7 @@ interface CustomModelDefinitionLike {
|
|
|
575
576
|
cost?: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
576
577
|
contextWindow?: number;
|
|
577
578
|
maxTokens?: number;
|
|
579
|
+
omitMaxOutputTokens?: boolean;
|
|
578
580
|
headers?: Record<string, string>;
|
|
579
581
|
compat?: Model<Api>["compat"];
|
|
580
582
|
contextPromotionTarget?: string;
|
|
@@ -597,6 +599,7 @@ type CustomModelOverlay = {
|
|
|
597
599
|
cost?: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
598
600
|
contextWindow?: number;
|
|
599
601
|
maxTokens?: number;
|
|
602
|
+
omitMaxOutputTokens?: boolean;
|
|
600
603
|
headers?: Record<string, string>;
|
|
601
604
|
compat?: Model<Api>["compat"];
|
|
602
605
|
contextPromotionTarget?: string;
|
|
@@ -667,6 +670,7 @@ function buildCustomModelOverlay(
|
|
|
667
670
|
cost: modelDef.cost,
|
|
668
671
|
contextWindow: modelDef.contextWindow,
|
|
669
672
|
maxTokens: modelDef.maxTokens,
|
|
673
|
+
omitMaxOutputTokens: modelDef.omitMaxOutputTokens,
|
|
670
674
|
headers: mergeCustomModelHeaders(providerHeaders, modelDef.headers, authHeader, providerApiKey),
|
|
671
675
|
compat: mergeCompat(providerCompat, modelDef.compat),
|
|
672
676
|
contextPromotionTarget: modelDef.contextPromotionTarget,
|
|
@@ -823,6 +827,7 @@ function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuil
|
|
|
823
827
|
resolvedModel.contextWindow ?? reference?.contextWindow ?? (options.useDefaults ? 128000 : undefined),
|
|
824
828
|
maxTokens: resolvedModel.maxTokens ?? reference?.maxTokens ?? (options.useDefaults ? 16384 : undefined),
|
|
825
829
|
headers: resolvedModel.headers,
|
|
830
|
+
omitMaxOutputTokens: resolvedModel.omitMaxOutputTokens ?? reference?.omitMaxOutputTokens,
|
|
826
831
|
compat: mergeCompat(reference?.compat, resolvedModel.compat),
|
|
827
832
|
contextPromotionTarget: resolvedModel.contextPromotionTarget,
|
|
828
833
|
premiumMultiplier: resolvedModel.premiumMultiplier,
|
|
@@ -1124,6 +1129,7 @@ export class ModelRegistry {
|
|
|
1124
1129
|
cost: customModel.cost ?? existingModel.cost,
|
|
1125
1130
|
contextWindow: customModel.contextWindow ?? existingModel.contextWindow,
|
|
1126
1131
|
maxTokens: customModel.maxTokens ?? existingModel.maxTokens,
|
|
1132
|
+
omitMaxOutputTokens: customModel.omitMaxOutputTokens ?? existingModel.omitMaxOutputTokens,
|
|
1127
1133
|
// Same-id custom definitions replace bundled transport behavior. Provider-level
|
|
1128
1134
|
// headers/compat were already folded into customModel during parsing; do not
|
|
1129
1135
|
// re-merge bundled transport metadata here.
|
|
@@ -41,6 +41,7 @@ export const OpenAICompatSchema = z.object({
|
|
|
41
41
|
openRouterRouting: OpenRouterRoutingSchema.optional(),
|
|
42
42
|
vercelGatewayRouting: VercelGatewayRoutingSchema.optional(),
|
|
43
43
|
extraBody: z.record(z.string(), z.unknown()).optional(),
|
|
44
|
+
cacheControlFormat: z.enum(["anthropic"]).optional(),
|
|
44
45
|
supportsStrictMode: z.boolean().optional(),
|
|
45
46
|
toolStrictMode: z.enum(["all_strict", "none"]).optional(),
|
|
46
47
|
});
|
|
@@ -92,6 +93,7 @@ const ModelDefinitionSchema = z.object({
|
|
|
92
93
|
premiumMultiplier: z.number().optional(),
|
|
93
94
|
contextWindow: z.number().optional(),
|
|
94
95
|
maxTokens: z.number().optional(),
|
|
96
|
+
omitMaxOutputTokens: z.boolean().optional(),
|
|
95
97
|
headers: z.record(z.string(), z.string()).optional(),
|
|
96
98
|
compat: OpenAICompatSchema.optional(),
|
|
97
99
|
contextPromotionTarget: z.string().min(1).optional(),
|
|
@@ -113,6 +115,7 @@ export const ModelOverrideSchema = z.object({
|
|
|
113
115
|
premiumMultiplier: z.number().optional(),
|
|
114
116
|
contextWindow: z.number().optional(),
|
|
115
117
|
maxTokens: z.number().optional(),
|
|
118
|
+
omitMaxOutputTokens: z.boolean().optional(),
|
|
116
119
|
headers: z.record(z.string(), z.string()).optional(),
|
|
117
120
|
compat: OpenAICompatSchema.optional(),
|
|
118
121
|
contextPromotionTarget: z.string().min(1).optional(),
|
|
@@ -3057,9 +3057,9 @@ export const SETTINGS_SCHEMA = {
|
|
|
3057
3057
|
tab: "providers",
|
|
3058
3058
|
label: "Append-Only Context",
|
|
3059
3059
|
description:
|
|
3060
|
-
"Cache system prompt + tool specs and keep an append-only message log so provider prefix caches (DeepSeek, Anthropic) hit at maximum rate. Auto enables for
|
|
3060
|
+
"Cache system prompt + tool specs and keep an append-only message log so provider prefix caches (DeepSeek, Xiaomi/SGLang, Anthropic) hit at maximum rate. Auto enables for known prefix-cache providers.",
|
|
3061
3061
|
options: [
|
|
3062
|
-
{ value: "auto", label: "Auto", description: "Enable for
|
|
3062
|
+
{ value: "auto", label: "Auto", description: "Enable for known prefix-cache providers (recommended)" },
|
|
3063
3063
|
{ value: "on", label: "On", description: "Always enable append-only context" },
|
|
3064
3064
|
{ value: "off", label: "Off", description: "Disable append-only context" },
|
|
3065
3065
|
],
|
package/src/config/settings.ts
CHANGED
|
@@ -907,6 +907,9 @@ const SETTING_HOOKS: Partial<Record<SettingPath, SettingHook<any>>> = {
|
|
|
907
907
|
for (const cb of appendOnlyModeCallbacks) cb(value);
|
|
908
908
|
}
|
|
909
909
|
},
|
|
910
|
+
"hindsight.bankId": () => fireHindsightScopeChanged(),
|
|
911
|
+
"hindsight.bankIdPrefix": () => fireHindsightScopeChanged(),
|
|
912
|
+
"hindsight.scoping": () => fireHindsightScopeChanged(),
|
|
910
913
|
};
|
|
911
914
|
/** Callbacks invoked when `provider.appendOnlyContext` changes at runtime. */
|
|
912
915
|
const appendOnlyModeCallbacks = new Set<(value: string) => void>();
|
|
@@ -923,6 +926,41 @@ export function onAppendOnlyModeChanged(cb: (value: string) => void): () => void
|
|
|
923
926
|
};
|
|
924
927
|
}
|
|
925
928
|
|
|
929
|
+
/** Callbacks fired when any `hindsight.bankId` / `bankIdPrefix` / `scoping` value changes. */
|
|
930
|
+
const hindsightScopeCallbacks = new Set<() => void>();
|
|
931
|
+
|
|
932
|
+
function fireHindsightScopeChanged(): void {
|
|
933
|
+
// Snapshot the callback set before invoking — a callback's body is allowed
|
|
934
|
+
// to subscribe a NEW callback (the Hindsight backend re-registers the
|
|
935
|
+
// fresh state's listener on every rebuild). Iterating the live Set would
|
|
936
|
+
// re-invoke those just-added callbacks within the same fire, which spins
|
|
937
|
+
// in place: subscribe → invoke → subscribe → invoke → …
|
|
938
|
+
for (const cb of [...hindsightScopeCallbacks]) {
|
|
939
|
+
try {
|
|
940
|
+
cb();
|
|
941
|
+
} catch (err) {
|
|
942
|
+
logger.warn("Settings: hindsight scope hook failed", { error: String(err) });
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Subscribe to changes in the Hindsight bank-scoping settings. Lets the
|
|
949
|
+
* Hindsight backend rebuild the active `HindsightSessionState` when the
|
|
950
|
+
* operator switches `hindsight.bankId`, `hindsight.bankIdPrefix`, or
|
|
951
|
+
* `hindsight.scoping` mid-session so subsequent retain/recall calls land in
|
|
952
|
+
* the new bank instead of the one selected at session start.
|
|
953
|
+
*
|
|
954
|
+
* Returns an unsubscribe function. The callback receives no arguments — the
|
|
955
|
+
* caller is expected to re-read the relevant settings via `Settings.get`.
|
|
956
|
+
*/
|
|
957
|
+
export function onHindsightScopeChanged(cb: () => void): () => void {
|
|
958
|
+
hindsightScopeCallbacks.add(cb);
|
|
959
|
+
return () => {
|
|
960
|
+
hindsightScopeCallbacks.delete(cb);
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
|
|
926
964
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
927
965
|
// Global Singleton
|
|
928
966
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -46,5 +46,6 @@ const doubled = value * 2;
|
|
|
46
46
|
- Callback identity matters.
|
|
47
47
|
- Type guard preserves narrowing.
|
|
48
48
|
- Public API, test seam, or DI boundary needs indirection.
|
|
49
|
+
- Names a non-obvious formula or magic-constant computation that the inlined expression would not explain on its own.
|
|
49
50
|
|
|
50
51
|
If none apply, inline it.
|
package/src/discovery/github.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* Capabilities:
|
|
11
11
|
* - context-files: copilot-instructions.md in .github/
|
|
12
12
|
* - instructions: *.instructions.md in .github/instructions/ with applyTo frontmatter
|
|
13
|
+
* - skills: <name>/SKILL.md in .github/skills/ (GitHub Agent Skills layout)
|
|
13
14
|
*/
|
|
14
15
|
import * as path from "node:path";
|
|
15
16
|
import { parseFrontmatter } from "@oh-my-pi/pi-utils";
|
|
@@ -17,9 +18,10 @@ import { registerProvider } from "../capability";
|
|
|
17
18
|
import { type ContextFile, contextFileCapability } from "../capability/context-file";
|
|
18
19
|
import { readFile } from "../capability/fs";
|
|
19
20
|
import { type Instruction, instructionCapability } from "../capability/instruction";
|
|
21
|
+
import { type Skill, skillCapability } from "../capability/skill";
|
|
20
22
|
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
|
21
23
|
|
|
22
|
-
import { calculateDepth, createSourceMeta, getProjectPath, loadFilesFromDir } from "./helpers";
|
|
24
|
+
import { calculateDepth, createSourceMeta, getProjectPath, loadFilesFromDir, scanSkillsFromDir } from "./helpers";
|
|
23
25
|
|
|
24
26
|
const PROVIDER_ID = "github";
|
|
25
27
|
const DISPLAY_NAME = "GitHub Copilot";
|
|
@@ -97,6 +99,32 @@ function transformInstruction(name: string, content: string, filePath: string, s
|
|
|
97
99
|
};
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
// =============================================================================
|
|
103
|
+
// Skills
|
|
104
|
+
// =============================================================================
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Load skills from `.github/skills/<name>/SKILL.md`.
|
|
108
|
+
*
|
|
109
|
+
* GitHub documents this layout for Copilot Agent Skills and matches the
|
|
110
|
+
* non-recursive shape `scanSkillsFromDir` already expects. `requireDescription`
|
|
111
|
+
* is on to match the Agent Skills spec (name + description are mandatory) and
|
|
112
|
+
* the sibling `native`/`omp-plugins` providers.
|
|
113
|
+
*
|
|
114
|
+
* @see https://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/customize-cloud-agent/add-skills
|
|
115
|
+
*/
|
|
116
|
+
async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
|
|
117
|
+
const skillsDir = getProjectPath(ctx, "github", "skills");
|
|
118
|
+
if (!skillsDir) return { items: [], warnings: [] };
|
|
119
|
+
|
|
120
|
+
return scanSkillsFromDir(ctx, {
|
|
121
|
+
dir: skillsDir,
|
|
122
|
+
providerId: PROVIDER_ID,
|
|
123
|
+
level: "project",
|
|
124
|
+
requireDescription: true,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
100
128
|
// =============================================================================
|
|
101
129
|
// Provider Registration
|
|
102
130
|
// =============================================================================
|
|
@@ -116,3 +144,11 @@ registerProvider(instructionCapability.id, {
|
|
|
116
144
|
priority: PRIORITY,
|
|
117
145
|
load: loadInstructions,
|
|
118
146
|
});
|
|
147
|
+
|
|
148
|
+
registerProvider<Skill>(skillCapability.id, {
|
|
149
|
+
id: PROVIDER_ID,
|
|
150
|
+
displayName: DISPLAY_NAME,
|
|
151
|
+
description: "Load skills from .github/skills/*/SKILL.md",
|
|
152
|
+
priority: PRIORITY,
|
|
153
|
+
load: loadSkills,
|
|
154
|
+
});
|
package/src/discovery/helpers.ts
CHANGED
|
@@ -212,6 +212,7 @@ export interface ParsedAgentFields {
|
|
|
212
212
|
output?: unknown;
|
|
213
213
|
thinkingLevel?: ThinkingLevel;
|
|
214
214
|
autoloadSkills?: string[];
|
|
215
|
+
readSummarize?: boolean;
|
|
215
216
|
blocking?: boolean;
|
|
216
217
|
}
|
|
217
218
|
|
|
@@ -265,10 +266,11 @@ export function parseAgentFields(frontmatter: Record<string, unknown>): ParsedAg
|
|
|
265
266
|
const thinkingLevel = parseThinkingLevel(rawThinkingLevel);
|
|
266
267
|
const model = parseModelList(frontmatter.model);
|
|
267
268
|
const blocking = parseBoolean(frontmatter.blocking);
|
|
269
|
+
const readSummarize = parseBoolean(frontmatter.readSummarize);
|
|
268
270
|
const autoloadSkills = parseArrayOrCSV(frontmatter.autoloadSkills)
|
|
269
271
|
?.map(s => s.trim())
|
|
270
272
|
.filter(Boolean);
|
|
271
|
-
return { name, description, tools, spawns, model, output, thinkingLevel, blocking, autoloadSkills };
|
|
273
|
+
return { name, description, tools, spawns, model, output, thinkingLevel, blocking, autoloadSkills, readSummarize };
|
|
272
274
|
}
|
|
273
275
|
|
|
274
276
|
async function globIf(
|
package/src/exa/mcp-client.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TSchema } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { $env, logger } from "@oh-my-pi/pi-utils";
|
|
3
3
|
import type { CustomTool, CustomToolResult } from "../extensibility/custom-tools/types";
|
|
4
|
-
import { callMCP } from "../mcp/json-rpc";
|
|
4
|
+
import { type CallMcpOptions, callMCP } from "../mcp/json-rpc";
|
|
5
5
|
import type {
|
|
6
6
|
ExaRenderDetails,
|
|
7
7
|
ExaSearchResponse,
|
|
@@ -105,15 +105,21 @@ export async function callExaTool(
|
|
|
105
105
|
toolName: string,
|
|
106
106
|
args: Record<string, unknown>,
|
|
107
107
|
apiKey: string | null,
|
|
108
|
+
options?: CallMcpOptions,
|
|
108
109
|
): Promise<unknown> {
|
|
109
110
|
const params = new URLSearchParams();
|
|
110
111
|
if (apiKey) params.set("exaApiKey", apiKey);
|
|
111
112
|
params.set("tools", toolName);
|
|
112
113
|
const url = `https://mcp.exa.ai/mcp?${params.toString()}`;
|
|
113
|
-
const response = (await callMCP(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
const response = (await callMCP(
|
|
115
|
+
url,
|
|
116
|
+
"tools/call",
|
|
117
|
+
{
|
|
118
|
+
name: toolName,
|
|
119
|
+
arguments: args,
|
|
120
|
+
},
|
|
121
|
+
options,
|
|
122
|
+
)) as MCPCallResponse;
|
|
117
123
|
|
|
118
124
|
if (response.error) {
|
|
119
125
|
logger.error("MCP tools/call error", { toolName, args, error: response.error });
|