@oh-my-pi/pi-coding-agent 16.1.23 → 16.2.1
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 +82 -0
- package/dist/cli.js +3785 -3695
- package/dist/types/advisor/__tests__/emission-guard.test.d.ts +1 -0
- package/dist/types/advisor/emission-guard.d.ts +73 -0
- package/dist/types/advisor/index.d.ts +1 -0
- package/dist/types/advisor/runtime.d.ts +7 -0
- package/dist/types/advisor/watchdog.d.ts +13 -0
- package/dist/types/cli/flag-tables.d.ts +1 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +1 -1
- package/dist/types/collab/display-name.d.ts +3 -0
- package/dist/types/collab/host.d.ts +0 -2
- package/dist/types/commands/worktree.d.ts +0 -3
- package/dist/types/config/inline-tool-descriptors-mode.d.ts +13 -0
- package/dist/types/config/model-discovery.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +6 -2
- package/dist/types/config/model-resolver.d.ts +12 -0
- package/dist/types/config/models-config-schema.d.ts +29 -2
- package/dist/types/config/models-config.d.ts +22 -1
- package/dist/types/config/settings-schema.d.ts +91 -21
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/dap/config.d.ts +1 -1
- package/dist/types/edit/hashline/filesystem.d.ts +4 -2
- package/dist/types/edit/renderer.d.ts +4 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -17
- package/dist/types/extensibility/hooks/types.d.ts +10 -10
- package/dist/types/extensibility/plugins/marketplace/types.d.ts +1 -0
- package/dist/types/internal-urls/__tests__/ssh-protocol.test.d.ts +1 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/registry-helpers.d.ts +2 -0
- package/dist/types/internal-urls/router.d.ts +1 -1
- package/dist/types/internal-urls/ssh-protocol.d.ts +10 -0
- package/dist/types/internal-urls/types.d.ts +19 -2
- package/dist/types/irc/bus.d.ts +6 -0
- package/dist/types/modes/acp/acp-event-mapper.d.ts +1 -0
- package/dist/types/modes/components/__tests__/move-overlay.test.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +7 -0
- package/dist/types/modes/components/move-overlay.d.ts +23 -0
- package/dist/types/modes/components/plugin-selector.d.ts +2 -1
- package/dist/types/modes/components/queue-mode-selector.d.ts +2 -1
- package/dist/types/modes/components/select-list-mouse-routing.d.ts +2 -0
- package/dist/types/modes/components/settings-defs.d.ts +4 -1
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/show-images-selector.d.ts +2 -1
- package/dist/types/modes/components/status-line/component.d.ts +2 -0
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/theme-selector.d.ts +2 -1
- package/dist/types/modes/components/thinking-selector.d.ts +2 -1
- package/dist/types/modes/controllers/command-controller.d.ts +10 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -1
- package/dist/types/modes/internal-url-autocomplete.d.ts +1 -1
- package/dist/types/modes/running-subagent-badge.d.ts +6 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +9 -1
- package/dist/types/sdk.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +8 -1
- package/dist/types/session/blob-store.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +13 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
- package/dist/types/ssh/__tests__/connection-manager-args.test.d.ts +1 -0
- package/dist/types/ssh/__tests__/file-transfer-posix-guard.test.d.ts +1 -0
- package/dist/types/ssh/connection-manager.d.ts +2 -0
- package/dist/types/ssh/file-transfer.d.ts +79 -0
- package/dist/types/ssh/utils.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +3 -0
- package/dist/types/task/executor.d.ts +16 -0
- package/dist/types/tiny/text.d.ts +1 -1
- package/dist/types/tools/builtin-names.d.ts +5 -1
- package/dist/types/tools/{find.d.ts → glob.d.ts} +15 -15
- package/dist/types/tools/{search.d.ts → grep.d.ts} +14 -14
- package/dist/types/tools/index.d.ts +3 -3
- package/dist/types/tools/path-utils.d.ts +29 -0
- package/dist/types/tools/read.d.ts +2 -2
- package/dist/types/tools/render-utils.d.ts +8 -0
- package/dist/types/tools/renderers.d.ts +11 -0
- package/dist/types/tools/ssh.d.ts +2 -0
- package/dist/types/tools/todo.d.ts +0 -16
- package/dist/types/tools/write.d.ts +2 -2
- package/dist/types/utils/active-repo-context.d.ts +8 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/utils/markit-cache.d.ts +23 -0
- package/dist/types/utils/markit.d.ts +5 -1
- package/dist/types/utils/prompt-path.d.ts +1 -0
- package/dist/types/web/search/providers/duckduckgo.d.ts +14 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +28 -0
- package/dist/types/web/search/providers/tinyfish.d.ts +29 -0
- package/dist/types/web/search/providers/xai.d.ts +13 -0
- package/dist/types/web/search/types.d.ts +18 -2
- package/package.json +30 -15
- package/scripts/build-binary.ts +9 -9
- package/scripts/bundle-dist.ts +2 -2
- package/src/advisor/__tests__/advisor.test.ts +61 -4
- package/src/advisor/__tests__/emission-guard.test.ts +147 -0
- package/src/advisor/advise-tool.ts +1 -1
- package/src/advisor/emission-guard.ts +172 -0
- package/src/advisor/index.ts +1 -0
- package/src/advisor/runtime.ts +11 -0
- package/src/advisor/watchdog.ts +27 -1
- package/src/cli/args.ts +5 -2
- package/src/cli/auth-broker-cli.ts +17 -0
- package/src/cli/config-cli.ts +4 -0
- package/src/cli/flag-tables.ts +7 -4
- package/src/cli/gallery-cli.ts +14 -2
- package/src/cli/gallery-fixtures/edit.ts +60 -0
- package/src/cli/gallery-fixtures/fs.ts +17 -17
- package/src/cli/gallery-fixtures/search.ts +4 -4
- package/src/cli/usage-cli.ts +20 -1
- package/src/cli/web-search-cli.ts +1 -1
- package/src/collab/display-name.ts +13 -0
- package/src/collab/guest.ts +5 -1
- package/src/collab/host.ts +2 -13
- package/src/commands/worktree.ts +6 -0
- package/src/config/inline-tool-descriptors-mode.ts +27 -0
- package/src/config/model-discovery.ts +77 -8
- package/src/config/model-registry.ts +89 -11
- package/src/config/model-resolver.ts +39 -1
- package/src/config/models-config-schema.ts +41 -5
- package/src/config/models-config.ts +4 -1
- package/src/config/settings-schema.ts +85 -27
- package/src/config/settings.ts +172 -1
- package/src/cursor.ts +1 -1
- package/src/dap/config.ts +152 -8
- package/src/dap/session.ts +1 -1
- package/src/discovery/claude-plugins.ts +3 -2
- package/src/discovery/helpers.ts +3 -1
- package/src/edit/hashline/diff.ts +14 -3
- package/src/edit/hashline/execute.ts +42 -6
- package/src/edit/hashline/filesystem.ts +38 -2
- package/src/edit/index.ts +1 -0
- package/src/edit/modes/patch.ts +11 -1
- package/src/edit/renderer.ts +140 -13
- package/src/eval/__tests__/agent-bridge.test.ts +101 -0
- package/src/eval/agent-bridge.ts +26 -3
- package/src/eval/js/tool-bridge.ts +2 -2
- package/src/export/html/index.ts +1 -1
- package/src/export/html/template.js +3 -1
- package/src/export/html/tool-views.generated.js +20 -20
- package/src/extensibility/extensions/types.ts +22 -22
- package/src/extensibility/hooks/types.ts +11 -11
- package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +8 -3
- package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +25 -10
- package/src/extensibility/plugins/marketplace/manager.ts +22 -0
- package/src/extensibility/plugins/marketplace/types.ts +1 -0
- package/src/internal-urls/__tests__/ssh-protocol.test.ts +331 -0
- package/src/internal-urls/docs-index.generated.txt +2 -2
- package/src/internal-urls/docs-index.ts +2 -3
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/registry-helpers.ts +19 -4
- package/src/internal-urls/router.ts +5 -3
- package/src/internal-urls/ssh-protocol.ts +367 -0
- package/src/internal-urls/types.ts +19 -2
- package/src/irc/bus.ts +11 -3
- package/src/mcp/tool-bridge.ts +32 -2
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-agent.ts +14 -1
- package/src/modes/acp/acp-event-mapper.ts +91 -27
- package/src/modes/components/__tests__/move-overlay.test.ts +166 -0
- package/src/modes/components/agent-hub.ts +3 -0
- package/src/modes/components/agent-transcript-viewer.ts +9 -7
- package/src/modes/components/custom-editor.ts +90 -5
- package/src/modes/components/move-overlay.ts +282 -0
- package/src/modes/components/plan-review-overlay.ts +35 -35
- package/src/modes/components/plugin-selector.ts +6 -1
- package/src/modes/components/queue-mode-selector.ts +6 -1
- package/src/modes/components/select-list-mouse-routing.ts +35 -0
- package/src/modes/components/session-selector.ts +11 -10
- package/src/modes/components/settings-defs.ts +14 -1
- package/src/modes/components/settings-selector.ts +196 -29
- package/src/modes/components/show-images-selector.ts +6 -1
- package/src/modes/components/status-line/component.ts +108 -28
- package/src/modes/components/status-line/segments.ts +5 -1
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/theme-selector.ts +6 -1
- package/src/modes/components/thinking-selector.ts +6 -1
- package/src/modes/components/tool-execution.ts +25 -9
- package/src/modes/components/tree-selector.ts +5 -5
- package/src/modes/controllers/command-controller.ts +140 -47
- package/src/modes/controllers/event-controller.ts +59 -3
- package/src/modes/controllers/input-controller.ts +68 -2
- package/src/modes/controllers/selector-controller.ts +5 -2
- package/src/modes/controllers/streaming-reveal.ts +17 -0
- package/src/modes/controllers/tool-args-reveal.ts +1 -1
- package/src/modes/interactive-mode.ts +116 -50
- package/src/modes/internal-url-autocomplete.ts +17 -2
- package/src/modes/prompt-action-autocomplete.ts +3 -1
- package/src/modes/running-subagent-badge.ts +13 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +2 -13
- package/src/modes/setup-wizard/scenes/providers.ts +2 -1
- package/src/modes/setup-wizard/scenes/theme.ts +6 -12
- package/src/modes/setup-wizard/scenes/web-search.ts +8 -13
- package/src/modes/setup-wizard/wizard-overlay.ts +6 -5
- package/src/modes/theme/theme.ts +9 -1
- package/src/modes/types.ts +9 -1
- package/src/modes/utils/interactive-context-helpers.ts +1 -1
- package/src/priority.json +15 -0
- package/src/prompts/advisor/active-repo-watchdog.md +6 -0
- package/src/prompts/advisor/context-files.md +8 -0
- package/src/prompts/advisor/system.md +21 -7
- package/src/prompts/agents/designer.md +1 -1
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/librarian.md +2 -2
- package/src/prompts/agents/plan.md +2 -2
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/agents/task.md +2 -2
- package/src/prompts/system/active-repo-context.md +4 -0
- package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
- package/src/prompts/system/plan-mode-active.md +3 -3
- package/src/prompts/system/project-prompt.md +2 -2
- package/src/prompts/system/system-prompt.md +6 -6
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/{find.md → glob.md} +1 -1
- package/src/prompts/tools/{search.md → grep.md} +3 -3
- package/src/prompts/tools/read.md +4 -2
- package/src/sdk.ts +65 -25
- package/src/session/agent-session.ts +519 -132
- package/src/session/blob-store.ts +24 -0
- package/src/session/session-history-format.ts +2 -2
- package/src/session/session-manager.ts +46 -0
- package/src/slash-commands/builtin-registry.ts +138 -20
- package/src/slash-commands/helpers/usage-report.ts +23 -2
- package/src/ssh/__tests__/connection-manager-args.test.ts +69 -0
- package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +68 -0
- package/src/ssh/connection-manager.ts +12 -15
- package/src/ssh/file-transfer.ts +209 -0
- package/src/ssh/utils.ts +24 -0
- package/src/system-prompt.ts +56 -28
- package/src/task/executor.ts +96 -36
- package/src/task/index.ts +3 -3
- package/src/task/render.ts +14 -13
- package/src/task/worktree.ts +38 -1
- package/src/tiny/text.ts +49 -4
- package/src/tiny/worker.ts +3 -3
- package/src/tools/bash.ts +2 -2
- package/src/tools/builtin-names.ts +26 -2
- package/src/tools/{find.ts → glob.ts} +48 -42
- package/src/tools/{search.ts → grep.ts} +311 -129
- package/src/tools/index.ts +13 -14
- package/src/tools/irc.ts +6 -2
- package/src/tools/path-utils.ts +64 -1
- package/src/tools/read.ts +35 -6
- package/src/tools/render-utils.ts +14 -0
- package/src/tools/renderers.ts +15 -4
- package/src/tools/ssh.ts +17 -2
- package/src/tools/todo.ts +4 -26
- package/src/tools/write.ts +25 -9
- package/src/utils/active-repo-context.ts +143 -0
- package/src/utils/image-resize.ts +88 -7
- package/src/utils/lang-from-path.ts +3 -3
- package/src/utils/markit-cache.ts +166 -0
- package/src/utils/markit.ts +86 -18
- package/src/utils/prompt-path.ts +3 -0
- package/src/utils/title-generator.ts +1 -1
- package/src/web/search/provider.ts +54 -34
- package/src/web/search/providers/duckduckgo.ts +140 -0
- package/src/web/search/providers/firecrawl.ts +144 -0
- package/src/web/search/providers/tinyfish.ts +159 -0
- package/src/web/search/providers/xai.ts +292 -0
- package/src/web/search/providers/zai.ts +142 -56
- package/src/web/search/types.ts +6 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function normalizePromptPath(value: string): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { SearchResponse } from "../../../web/search/types";
|
|
3
|
+
import type { SearchParams } from "./base";
|
|
4
|
+
import { SearchProvider } from "./base";
|
|
5
|
+
/** Execute DuckDuckGo Instant Answer API search. */
|
|
6
|
+
export declare function searchDuckDuckGo(params: SearchParams): Promise<SearchResponse>;
|
|
7
|
+
/** Search provider for DuckDuckGo Instant Answer API. */
|
|
8
|
+
export declare class DuckDuckGoProvider extends SearchProvider {
|
|
9
|
+
readonly id = "duckduckgo";
|
|
10
|
+
readonly label = "DuckDuckGo";
|
|
11
|
+
isAvailable(_authStorage: AuthStorage): boolean;
|
|
12
|
+
isExplicitlyAvailable(_authStorage: AuthStorage): boolean;
|
|
13
|
+
search(params: SearchParams): Promise<SearchResponse>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firecrawl Web Search Provider
|
|
3
|
+
*
|
|
4
|
+
* Calls Firecrawl's search API and maps web results into the unified
|
|
5
|
+
* SearchResponse shape used by the web search tool.
|
|
6
|
+
*/
|
|
7
|
+
import { type AuthStorage, type FetchImpl } from "@oh-my-pi/pi-ai";
|
|
8
|
+
import type { SearchResponse } from "../../../web/search/types";
|
|
9
|
+
import type { SearchParams } from "./base";
|
|
10
|
+
import { SearchProvider } from "./base";
|
|
11
|
+
export interface FirecrawlSearchParams {
|
|
12
|
+
query: string;
|
|
13
|
+
num_results?: number;
|
|
14
|
+
recency?: SearchParams["recency"];
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
fetch?: FetchImpl;
|
|
17
|
+
}
|
|
18
|
+
/** Resolve Firecrawl API key through the shared auth storage pipeline. */
|
|
19
|
+
export declare function findApiKey(authStorage: AuthStorage, sessionId?: string, signal?: AbortSignal): Promise<string | undefined>;
|
|
20
|
+
/** Execute Firecrawl web search. */
|
|
21
|
+
export declare function searchFirecrawl(params: SearchParams): Promise<SearchResponse>;
|
|
22
|
+
/** Search provider for Firecrawl web search. */
|
|
23
|
+
export declare class FirecrawlProvider extends SearchProvider {
|
|
24
|
+
readonly id = "firecrawl";
|
|
25
|
+
readonly label = "Firecrawl";
|
|
26
|
+
isAvailable(authStorage: AuthStorage): boolean;
|
|
27
|
+
search(params: SearchParams): Promise<SearchResponse>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TinyFish Web Search Provider
|
|
3
|
+
*
|
|
4
|
+
* Calls TinyFish's search API and maps results into the unified
|
|
5
|
+
* SearchResponse shape used by the web search tool.
|
|
6
|
+
*/
|
|
7
|
+
import { type AuthStorage, type FetchImpl } from "@oh-my-pi/pi-ai";
|
|
8
|
+
import type { SearchResponse } from "../../../web/search/types";
|
|
9
|
+
import type { SearchParams } from "./base";
|
|
10
|
+
import { SearchProvider } from "./base";
|
|
11
|
+
export interface TinyFishSearchParams {
|
|
12
|
+
query: string;
|
|
13
|
+
num_results?: number;
|
|
14
|
+
recency?: SearchParams["recency"];
|
|
15
|
+
page?: number;
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
fetch?: FetchImpl;
|
|
18
|
+
}
|
|
19
|
+
/** Resolve TinyFish API key through the shared auth storage pipeline. */
|
|
20
|
+
export declare function findApiKey(authStorage: AuthStorage, sessionId?: string, signal?: AbortSignal): Promise<string | undefined>;
|
|
21
|
+
/** Execute TinyFish web search. */
|
|
22
|
+
export declare function searchTinyFish(params: SearchParams): Promise<SearchResponse>;
|
|
23
|
+
/** Search provider for TinyFish web search. */
|
|
24
|
+
export declare class TinyFishProvider extends SearchProvider {
|
|
25
|
+
readonly id = "tinyfish";
|
|
26
|
+
readonly label = "TinyFish";
|
|
27
|
+
isAvailable(authStorage: AuthStorage): boolean;
|
|
28
|
+
search(params: SearchParams): Promise<SearchResponse>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AuthStorage } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { SearchResponse } from "../../../web/search/types";
|
|
3
|
+
import type { SearchParams } from "./base";
|
|
4
|
+
import { SearchProvider } from "./base";
|
|
5
|
+
/** Execute xAI Responses API web search. */
|
|
6
|
+
export declare function searchXAI(params: SearchParams): Promise<SearchResponse>;
|
|
7
|
+
/** Search provider for xAI web search. */
|
|
8
|
+
export declare class XAIProvider extends SearchProvider {
|
|
9
|
+
readonly id = "xai";
|
|
10
|
+
readonly label = "xAI";
|
|
11
|
+
isAvailable(authStorage: AuthStorage): boolean;
|
|
12
|
+
search(params: SearchParams): Promise<SearchResponse>;
|
|
13
|
+
}
|
|
@@ -23,6 +23,10 @@ export declare const SEARCH_PROVIDER_OPTIONS: readonly [{
|
|
|
23
23
|
readonly value: "codex";
|
|
24
24
|
readonly label: "OpenAI";
|
|
25
25
|
readonly description: "OpenAI's native web_search (uses ChatGPT OAuth via /login openai-codex)";
|
|
26
|
+
}, {
|
|
27
|
+
readonly value: "xai";
|
|
28
|
+
readonly label: "xAI";
|
|
29
|
+
readonly description: "Grok web search via xAI Responses API (requires XAI_API_KEY)";
|
|
26
30
|
}, {
|
|
27
31
|
readonly value: "zai";
|
|
28
32
|
readonly label: "Z.AI";
|
|
@@ -31,6 +35,10 @@ export declare const SEARCH_PROVIDER_OPTIONS: readonly [{
|
|
|
31
35
|
readonly value: "exa";
|
|
32
36
|
readonly label: "Exa";
|
|
33
37
|
readonly description: "Uses Exa API when EXA_API_KEY is set; falls back to Exa MCP";
|
|
38
|
+
}, {
|
|
39
|
+
readonly value: "tinyfish";
|
|
40
|
+
readonly label: "TinyFish";
|
|
41
|
+
readonly description: "Requires TINYFISH_API_KEY";
|
|
34
42
|
}, {
|
|
35
43
|
readonly value: "jina";
|
|
36
44
|
readonly label: "Jina";
|
|
@@ -43,6 +51,10 @@ export declare const SEARCH_PROVIDER_OPTIONS: readonly [{
|
|
|
43
51
|
readonly value: "tavily";
|
|
44
52
|
readonly label: "Tavily";
|
|
45
53
|
readonly description: "Requires TAVILY_API_KEY";
|
|
54
|
+
}, {
|
|
55
|
+
readonly value: "firecrawl";
|
|
56
|
+
readonly label: "Firecrawl";
|
|
57
|
+
readonly description: "Requires FIRECRAWL_API_KEY";
|
|
46
58
|
}, {
|
|
47
59
|
readonly value: "brave";
|
|
48
60
|
readonly label: "Brave";
|
|
@@ -63,6 +75,10 @@ export declare const SEARCH_PROVIDER_OPTIONS: readonly [{
|
|
|
63
75
|
readonly value: "searxng";
|
|
64
76
|
readonly label: "SearXNG";
|
|
65
77
|
readonly description: "Requires SEARXNG_ENDPOINT or searxng.endpoint";
|
|
78
|
+
}, {
|
|
79
|
+
readonly value: "duckduckgo";
|
|
80
|
+
readonly label: "DuckDuckGo";
|
|
81
|
+
readonly description: "Uses DuckDuckGo Instant Answer API (no API key)";
|
|
66
82
|
}];
|
|
67
83
|
/** Supported web search providers (every option except `auto`). */
|
|
68
84
|
export type SearchProviderId = Exclude<(typeof SEARCH_PROVIDER_OPTIONS)[number]["value"], "auto">;
|
|
@@ -88,7 +104,7 @@ export interface SearchSource {
|
|
|
88
104
|
ageSeconds?: number;
|
|
89
105
|
author?: string;
|
|
90
106
|
}
|
|
91
|
-
/** Citation with text reference (
|
|
107
|
+
/** Citation with text reference (LLM-mediated providers) */
|
|
92
108
|
export interface SearchCitation {
|
|
93
109
|
url: string;
|
|
94
110
|
title: string;
|
|
@@ -106,7 +122,7 @@ export interface SearchUsage {
|
|
|
106
122
|
/** Unified response across providers */
|
|
107
123
|
export interface SearchResponse {
|
|
108
124
|
provider: SearchProviderId | "none";
|
|
109
|
-
/** Synthesized answer text (
|
|
125
|
+
/** Synthesized answer text (LLM-mediated providers) */
|
|
110
126
|
answer?: string;
|
|
111
127
|
/** Search result sources */
|
|
112
128
|
sources: SearchSource[];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "16.1
|
|
4
|
+
"version": "16.2.1",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -39,26 +39,33 @@
|
|
|
39
39
|
"fix": "biome check --write --unsafe . && bun run format-prompts",
|
|
40
40
|
"fmt": "biome format --write . && bun run format-prompts",
|
|
41
41
|
"format-prompts": "bun scripts/format-prompts.ts",
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
42
|
+
"gen:docs": "bun scripts/generate-docs-index.ts --generate",
|
|
43
|
+
"gen:docs:reset": "bun scripts/generate-docs-index.ts --reset",
|
|
44
|
+
"gen:tool-views": "bun --cwd=../collab-web run gen:tool-views",
|
|
45
|
+
"gen:bundle": "bun scripts/bundle-dist.ts",
|
|
46
|
+
"gen:mupdf": "bun scripts/embed-mupdf-wasm.ts --generate",
|
|
47
|
+
"gen:mupdf:reset": "bun scripts/embed-mupdf-wasm.ts --reset",
|
|
48
|
+
"gen:native": "bun --cwd=../natives run gen:native",
|
|
49
|
+
"gen:native:reset": "bun --cwd=../natives run gen:native:reset",
|
|
50
|
+
"prepack": "bun run gen:docs && bun run gen:tool-views && bun run gen:bundle || ( bun run gen:docs:reset; exit 1 )",
|
|
51
|
+
"postpack": "bun run gen:docs:reset",
|
|
45
52
|
"bench:guard": "bun scripts/bench-guard.ts"
|
|
46
53
|
},
|
|
47
54
|
"dependencies": {
|
|
48
55
|
"@agentclientprotocol/sdk": "0.25.0",
|
|
49
56
|
"@babel/parser": "^7.29.7",
|
|
50
57
|
"@mozilla/readability": "^0.6.0",
|
|
51
|
-
"@oh-my-pi/hashline": "16.1
|
|
52
|
-
"@oh-my-pi/omp-stats": "16.1
|
|
53
|
-
"@oh-my-pi/pi-agent-core": "16.1
|
|
54
|
-
"@oh-my-pi/pi-ai": "16.1
|
|
55
|
-
"@oh-my-pi/pi-catalog": "16.1
|
|
56
|
-
"@oh-my-pi/pi-mnemopi": "16.1
|
|
57
|
-
"@oh-my-pi/pi-natives": "16.1
|
|
58
|
-
"@oh-my-pi/pi-tui": "16.1
|
|
59
|
-
"@oh-my-pi/pi-utils": "16.1
|
|
60
|
-
"@oh-my-pi/pi-wire": "16.1
|
|
61
|
-
"@oh-my-pi/snapcompact": "16.1
|
|
58
|
+
"@oh-my-pi/hashline": "16.2.1",
|
|
59
|
+
"@oh-my-pi/omp-stats": "16.2.1",
|
|
60
|
+
"@oh-my-pi/pi-agent-core": "16.2.1",
|
|
61
|
+
"@oh-my-pi/pi-ai": "16.2.1",
|
|
62
|
+
"@oh-my-pi/pi-catalog": "16.2.1",
|
|
63
|
+
"@oh-my-pi/pi-mnemopi": "16.2.1",
|
|
64
|
+
"@oh-my-pi/pi-natives": "16.2.1",
|
|
65
|
+
"@oh-my-pi/pi-tui": "16.2.1",
|
|
66
|
+
"@oh-my-pi/pi-utils": "16.2.1",
|
|
67
|
+
"@oh-my-pi/pi-wire": "16.2.1",
|
|
68
|
+
"@oh-my-pi/snapcompact": "16.2.1",
|
|
62
69
|
"@opentelemetry/api": "^1.9.1",
|
|
63
70
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
64
71
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
|
@@ -375,6 +382,14 @@
|
|
|
375
382
|
"types": "./dist/types/lsp/clients/*.d.ts",
|
|
376
383
|
"import": "./src/lsp/clients/*.ts"
|
|
377
384
|
},
|
|
385
|
+
"./markit": {
|
|
386
|
+
"types": "./dist/types/markit/index.d.ts",
|
|
387
|
+
"import": "./src/markit/index.ts"
|
|
388
|
+
},
|
|
389
|
+
"./markit/*": {
|
|
390
|
+
"types": "./dist/types/markit/*.d.ts",
|
|
391
|
+
"import": "./src/markit/*.ts"
|
|
392
|
+
},
|
|
378
393
|
"./mcp": {
|
|
379
394
|
"types": "./dist/types/mcp/index.d.ts",
|
|
380
395
|
"import": "./src/mcp/index.ts"
|
package/scripts/build-binary.ts
CHANGED
|
@@ -49,8 +49,8 @@ async function main(): Promise<void> {
|
|
|
49
49
|
// Generate inside the try so the finally always restores the empty checked-in
|
|
50
50
|
// placeholders (stats client archive, docs index) even on failure.
|
|
51
51
|
try {
|
|
52
|
-
await runCommand(["bun", "--cwd=../stats", "
|
|
53
|
-
await runCommand(["bun", "
|
|
52
|
+
await runCommand(["bun", "--cwd=../stats", "run", "gen:stats"]);
|
|
53
|
+
await runCommand(["bun", "run", "gen:docs"]);
|
|
54
54
|
// `legacy-pi-bundled-registry.ts` static-imports
|
|
55
55
|
// `@oh-my-pi/pi-coding-agent/export/html` (one of pi-coding-agent's
|
|
56
56
|
// named subpath exports, see scripts/generate-legacy-pi-bundled-registry.ts),
|
|
@@ -61,14 +61,14 @@ async function main(): Promise<void> {
|
|
|
61
61
|
// resolving the missing generated bundle. Rebuilding the tool views
|
|
62
62
|
// here makes the compile self-contained and matches what `prepack`
|
|
63
63
|
// does for the npm bundle.
|
|
64
|
-
await runCommand(["bun", "--cwd=../collab-web", "run", "
|
|
64
|
+
await runCommand(["bun", "--cwd=../collab-web", "run", "gen:tool-views"]);
|
|
65
65
|
await runCommand(
|
|
66
|
-
["bun", "--cwd=../natives", "run", "
|
|
66
|
+
["bun", "--cwd=../natives", "run", "gen:native"],
|
|
67
67
|
crossTarget
|
|
68
68
|
? { ...Bun.env, TARGET_PLATFORM: crossPlatform as string, TARGET_ARCH: crossArch as string }
|
|
69
69
|
: Bun.env,
|
|
70
70
|
);
|
|
71
|
-
await runCommand(["bun", "
|
|
71
|
+
await runCommand(["bun", "run", "gen:mupdf"]);
|
|
72
72
|
// Regenerate the bundled-pi registry + key set before the compile so any
|
|
73
73
|
// new pi-* subpath export added under `packages/*/package.json` is served
|
|
74
74
|
// from the host's in-process copy. Without this, `bun build --compile`
|
|
@@ -123,12 +123,12 @@ async function main(): Promise<void> {
|
|
|
123
123
|
await runCommand(["codesign", "--force", "--sign", "-", outputPath]);
|
|
124
124
|
}
|
|
125
125
|
} finally {
|
|
126
|
-
await runCommand(["bun", "
|
|
127
|
-
await runCommand(["bun", "--cwd=../natives", "run", "
|
|
126
|
+
await runCommand(["bun", "run", "gen:mupdf:reset"]);
|
|
127
|
+
await runCommand(["bun", "--cwd=../natives", "run", "gen:native:reset"]);
|
|
128
128
|
}
|
|
129
129
|
} finally {
|
|
130
|
-
await runCommand(["bun", "--cwd=../stats", "
|
|
131
|
-
await runCommand(["bun", "
|
|
130
|
+
await runCommand(["bun", "--cwd=../stats", "run", "gen:stats:reset"]);
|
|
131
|
+
await runCommand(["bun", "run", "gen:docs:reset"]);
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
|
package/scripts/bundle-dist.ts
CHANGED
|
@@ -79,7 +79,7 @@ async function main(): Promise<void> {
|
|
|
79
79
|
// so embed the dashboard archive the same way compiled binaries do
|
|
80
80
|
// (scripts/build-binary.ts). Reset afterwards to keep the checked-in
|
|
81
81
|
// placeholder empty.
|
|
82
|
-
await runCommand(["bun", "--cwd=../stats", "
|
|
82
|
+
await runCommand(["bun", "--cwd=../stats", "run", "gen:stats"]);
|
|
83
83
|
try {
|
|
84
84
|
await runCommand([
|
|
85
85
|
"bun",
|
|
@@ -97,7 +97,7 @@ async function main(): Promise<void> {
|
|
|
97
97
|
"./src/cli.ts",
|
|
98
98
|
]);
|
|
99
99
|
} finally {
|
|
100
|
-
await runCommand(["bun", "--cwd=../stats", "
|
|
100
|
+
await runCommand(["bun", "--cwd=../stats", "run", "gen:stats:reset"]);
|
|
101
101
|
}
|
|
102
102
|
await ensureShebang();
|
|
103
103
|
const stat = await fs.stat(cliPath);
|
|
@@ -3,6 +3,7 @@ import type { AgentMessage, AgentTelemetryConfig } from "@oh-my-pi/pi-agent-core
|
|
|
3
3
|
import { type } from "arktype";
|
|
4
4
|
import { createAdvisorMessageCard } from "../../modes/components/advisor-message";
|
|
5
5
|
import { getThemeByName } from "../../modes/theme/theme";
|
|
6
|
+
import advisorSystemPrompt from "../../prompts/advisor/system.md" with { type: "text" };
|
|
6
7
|
import { SecretObfuscator } from "../../secrets/obfuscator";
|
|
7
8
|
import { formatSessionHistoryMarkdown } from "../../session/session-history-format";
|
|
8
9
|
import { YieldQueue } from "../../session/yield-queue";
|
|
@@ -15,12 +16,68 @@ import {
|
|
|
15
16
|
type AdvisorRuntimeHost,
|
|
16
17
|
deriveAdvisorTelemetry,
|
|
17
18
|
formatAdvisorBatchContent,
|
|
19
|
+
formatAdvisorContextPrompt,
|
|
18
20
|
isAdvisorInterruptImmuneTurnActive,
|
|
19
21
|
isInterruptingSeverity,
|
|
20
22
|
resolveAdvisorDeliveryChannel,
|
|
21
23
|
} from "..";
|
|
22
24
|
|
|
23
25
|
describe("advisor", () => {
|
|
26
|
+
describe("advisor system prompt", () => {
|
|
27
|
+
it("forbids concrete claims about tool arguments hidden from the advisor transcript", () => {
|
|
28
|
+
const messages = [
|
|
29
|
+
{
|
|
30
|
+
role: "assistant",
|
|
31
|
+
content: [
|
|
32
|
+
{
|
|
33
|
+
type: "toolCall",
|
|
34
|
+
id: "search-timeout",
|
|
35
|
+
name: "grep",
|
|
36
|
+
arguments: { pattern: "needle", paths: ["packages/coding-agent/src"] },
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
timestamp: 1,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
role: "toolResult",
|
|
43
|
+
toolCallId: "search-timeout",
|
|
44
|
+
toolName: "grep",
|
|
45
|
+
content: [{ type: "text", text: "timed out after 30s" }],
|
|
46
|
+
isError: true,
|
|
47
|
+
timestamp: 2,
|
|
48
|
+
},
|
|
49
|
+
] as unknown as AgentMessage[];
|
|
50
|
+
|
|
51
|
+
const rendered = formatSessionHistoryMarkdown(messages);
|
|
52
|
+
|
|
53
|
+
expect(rendered).toContain("→ grep(needle @ packages/coding-agent/src) ⇒ error");
|
|
54
|
+
expect(rendered).not.toContain("paths[0]");
|
|
55
|
+
expect(advisorSystemPrompt).toContain("Arguments absent from the rendered transcript are UNKNOWN");
|
|
56
|
+
expect(advisorSystemPrompt).toContain("NEVER assert concrete values, array indexes");
|
|
57
|
+
expect(advisorSystemPrompt).toContain("NEVER claim `paths[0]`, array flattening, or malformed `paths`");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("formatAdvisorContextPrompt", () => {
|
|
62
|
+
it("renders project context files into a block with path and verbatim content", () => {
|
|
63
|
+
const rendered = formatAdvisorContextPrompt([
|
|
64
|
+
{
|
|
65
|
+
path: "/repo/AGENTS.md",
|
|
66
|
+
content: "Use `bun check`, never `tsc`.\nNo `any` unless absolutely necessary.",
|
|
67
|
+
},
|
|
68
|
+
]);
|
|
69
|
+
expect(rendered).toBeDefined();
|
|
70
|
+
expect(rendered).toContain('<file path="/repo/AGENTS.md">');
|
|
71
|
+
// Content is injected verbatim (noEscape) so backticks/markup survive for the model.
|
|
72
|
+
expect(rendered).toContain("Use `bun check`, never `tsc`.");
|
|
73
|
+
expect(rendered).toContain("No `any` unless absolutely necessary.");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("returns undefined when there are no context files", () => {
|
|
77
|
+
expect(formatAdvisorContextPrompt([])).toBeUndefined();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
24
81
|
describe("formatSessionHistoryMarkdown includeThinking", () => {
|
|
25
82
|
it("includes thinking text when includeThinking is true", () => {
|
|
26
83
|
const thinking = "I should check the edge case first.";
|
|
@@ -675,13 +732,13 @@ describe("advisor", () => {
|
|
|
675
732
|
} as AgentMessage,
|
|
676
733
|
{
|
|
677
734
|
role: "assistant",
|
|
678
|
-
content: [{ type: "toolCall", id: "b", name: "
|
|
735
|
+
content: [{ type: "toolCall", id: "b", name: "grep", arguments: { pattern: "y" } }],
|
|
679
736
|
timestamp: 4,
|
|
680
737
|
} as unknown as AgentMessage,
|
|
681
738
|
{
|
|
682
739
|
role: "toolResult",
|
|
683
740
|
toolCallId: "b",
|
|
684
|
-
toolName: "
|
|
741
|
+
toolName: "grep",
|
|
685
742
|
content: [{ type: "text", text: "ok" }],
|
|
686
743
|
isError: false,
|
|
687
744
|
timestamp: 5,
|
|
@@ -1008,9 +1065,9 @@ describe("advisor", () => {
|
|
|
1008
1065
|
|
|
1009
1066
|
describe("read-only tool allowlist", () => {
|
|
1010
1067
|
it("selects only the investigation tools from a mixed toolset", () => {
|
|
1011
|
-
const toolset = ["read", "edit", "
|
|
1068
|
+
const toolset = ["read", "edit", "grep", "bash", "glob", "write", "advise"];
|
|
1012
1069
|
const selected = toolset.filter(name => ADVISOR_READONLY_TOOL_NAMES.has(name));
|
|
1013
|
-
expect(selected).toEqual(["read", "
|
|
1070
|
+
expect(selected).toEqual(["read", "grep", "glob"]);
|
|
1014
1071
|
expect(ADVISOR_READONLY_TOOL_NAMES.has("edit")).toBe(false);
|
|
1015
1072
|
expect(ADVISOR_READONLY_TOOL_NAMES.has("bash")).toBe(false);
|
|
1016
1073
|
expect(ADVISOR_READONLY_TOOL_NAMES.has("write")).toBe(false);
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { AdvisorEmissionGuard, normalizeAdvisorNote } from "../emission-guard";
|
|
3
|
+
|
|
4
|
+
describe("normalizeAdvisorNote", () => {
|
|
5
|
+
it("collapses punctuation, casing, and surrounding whitespace into one canonical key", () => {
|
|
6
|
+
// The reporter's three top duplicates all key to the same canonical form
|
|
7
|
+
// regardless of trailing punctuation or casing — that's what makes the
|
|
8
|
+
// dedupe + suppression checks single-membership.
|
|
9
|
+
expect(normalizeAdvisorNote("Stop.")).toBe("stop");
|
|
10
|
+
expect(normalizeAdvisorNote(" STOP! ")).toBe("stop");
|
|
11
|
+
expect(normalizeAdvisorNote("*Stop*")).toBe("stop");
|
|
12
|
+
expect(normalizeAdvisorNote("Done.")).toBe("done");
|
|
13
|
+
expect(normalizeAdvisorNote("No issue; continue.")).toBe("no issue continue");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("returns empty string for whitespace-only input so callers can short-circuit", () => {
|
|
17
|
+
expect(normalizeAdvisorNote("")).toBe("");
|
|
18
|
+
expect(normalizeAdvisorNote(" ")).toBe("");
|
|
19
|
+
expect(normalizeAdvisorNote("...")).toBe("");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("preserves internal letters/digits but folds non-alphanumeric runs to one space", () => {
|
|
23
|
+
expect(normalizeAdvisorNote("Refactor `auth-flow.ts`: drop legacy branch.")).toBe(
|
|
24
|
+
"refactor auth flow ts drop legacy branch",
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("AdvisorEmissionGuard", () => {
|
|
30
|
+
it("drops the exact content-free filler the reporter observed flooding the chat", () => {
|
|
31
|
+
// Issue #3520: 114× "Stop.", 52× "No issue; continue.", 41× "Done." —
|
|
32
|
+
// none of these carry a concrete reason and they cannot be acted on, so
|
|
33
|
+
// the guard suppresses them regardless of severity.
|
|
34
|
+
const guard = new AdvisorEmissionGuard();
|
|
35
|
+
expect(guard.accept("Stop.")).toBe(false);
|
|
36
|
+
expect(guard.accept("Done.")).toBe(false);
|
|
37
|
+
expect(guard.accept("No issue; continue.")).toBe(false);
|
|
38
|
+
expect(guard.accept("LGTM")).toBe(false);
|
|
39
|
+
expect(guard.accept("No further watcher input needed.")).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("dedupes by normalized text across the session, ignoring casing and trailing punctuation", () => {
|
|
43
|
+
const guard = new AdvisorEmissionGuard();
|
|
44
|
+
expect(guard.accept("Move retries into the queue, not the request path.")).toBe(true);
|
|
45
|
+
// Same advice with different casing and trailing punctuation must NOT
|
|
46
|
+
// land twice in the primary transcript.
|
|
47
|
+
expect(guard.accept("move retries into the queue, not the request path")).toBe(false);
|
|
48
|
+
expect(guard.accept("Move retries into the queue, not the request path!")).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("rate-limits to one accepted advise per advisor update cycle", () => {
|
|
52
|
+
// The advisor system prompt says "at most one `advise` per update". Real
|
|
53
|
+
// models violate this; the guard enforces it at the boundary so the
|
|
54
|
+
// primary transcript never receives two advisories from one model cycle.
|
|
55
|
+
const guard = new AdvisorEmissionGuard();
|
|
56
|
+
expect(guard.accept("First concern: missing await in #handleRetry.")).toBe(true);
|
|
57
|
+
expect(guard.accept("Second concern: wrong env var name.")).toBe(false);
|
|
58
|
+
guard.beginUpdate();
|
|
59
|
+
// New cycle: budget reset.
|
|
60
|
+
expect(guard.accept("Second concern: wrong env var name.")).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("does not let a suppressed call consume the per-update budget", () => {
|
|
64
|
+
// A noise call like "Stop." must never displace a real concern that
|
|
65
|
+
// follows in the same advisor model cycle.
|
|
66
|
+
const guard = new AdvisorEmissionGuard();
|
|
67
|
+
expect(guard.accept("Stop.")).toBe(false);
|
|
68
|
+
expect(guard.accept("Concrete: read race in #handleRetry.")).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("does not let a deduped call consume the per-update budget", () => {
|
|
72
|
+
// A repeat of a prior session note is dropped, but the model can still
|
|
73
|
+
// follow it with a fresh concrete concern in the same cycle.
|
|
74
|
+
const guard = new AdvisorEmissionGuard();
|
|
75
|
+
expect(guard.accept("Concrete: read race in #handleRetry.")).toBe(true);
|
|
76
|
+
guard.beginUpdate();
|
|
77
|
+
expect(guard.accept("Concrete: read race in #handleRetry.")).toBe(false);
|
|
78
|
+
expect(guard.accept("New concern: cache eviction never fires.")).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("reset clears dedupe and the per-update gate so a re-primed advisor can re-raise old issues", () => {
|
|
82
|
+
// Compaction / session-switch rewrites the primary transcript. The
|
|
83
|
+
// advisor is re-primed from scratch and may legitimately re-raise the
|
|
84
|
+
// same concerns — they're new context for a freshly-primed reviewer.
|
|
85
|
+
const guard = new AdvisorEmissionGuard();
|
|
86
|
+
expect(guard.accept("Race in #handleRetry.")).toBe(true);
|
|
87
|
+
expect(guard.accept("Race in #handleRetry.")).toBe(false);
|
|
88
|
+
guard.reset();
|
|
89
|
+
expect(guard.accept("Race in #handleRetry.")).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("evicts oldest entries when dedupe history exceeds capacity", () => {
|
|
93
|
+
// Bounded so very long sessions cannot grow the dedupe state without
|
|
94
|
+
// bound. Pre-eviction unique notes are remembered; post-eviction the
|
|
95
|
+
// oldest one is forgotten and can resurface.
|
|
96
|
+
const guard = new AdvisorEmissionGuard({ capacity: 3 });
|
|
97
|
+
expect(guard.accept("first")).toBe(true);
|
|
98
|
+
guard.beginUpdate();
|
|
99
|
+
expect(guard.accept("second")).toBe(true);
|
|
100
|
+
guard.beginUpdate();
|
|
101
|
+
expect(guard.accept("third")).toBe(true);
|
|
102
|
+
guard.beginUpdate();
|
|
103
|
+
// "first" still in history.
|
|
104
|
+
expect(guard.accept("first")).toBe(false);
|
|
105
|
+
guard.beginUpdate();
|
|
106
|
+
// Fourth unique entry evicts "first".
|
|
107
|
+
expect(guard.accept("fourth")).toBe(true);
|
|
108
|
+
guard.beginUpdate();
|
|
109
|
+
expect(guard.accept("first")).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("rejects empty / whitespace-only notes without consuming the budget", () => {
|
|
113
|
+
const guard = new AdvisorEmissionGuard();
|
|
114
|
+
expect(guard.accept("")).toBe(false);
|
|
115
|
+
expect(guard.accept(" ")).toBe(false);
|
|
116
|
+
expect(guard.accept("Concrete advice.")).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("end-to-end: the reporter's 309-call spam log produces ≤1 accepted note across many updates", () => {
|
|
120
|
+
// Mimic the issue's distribution: 114× "Stop.", 52× "No issue; continue.",
|
|
121
|
+
// 41× "Done.", plus 102 copies of one concrete-but-repeated nit. Spread
|
|
122
|
+
// the calls across 50 advisor update cycles. Each cycle is allowed at
|
|
123
|
+
// most one accepted note, and identical-text repeats never escape the
|
|
124
|
+
// guard. After all calls, exactly the concrete nit has been accepted
|
|
125
|
+
// — and only once.
|
|
126
|
+
const guard = new AdvisorEmissionGuard();
|
|
127
|
+
const accepted: string[] = [];
|
|
128
|
+
const stream: string[] = [
|
|
129
|
+
...Array(114).fill("Stop."),
|
|
130
|
+
...Array(52).fill("No issue; continue."),
|
|
131
|
+
...Array(41).fill("Done."),
|
|
132
|
+
...Array(102).fill("Concrete-but-repeated nit: x"),
|
|
133
|
+
];
|
|
134
|
+
// Interleave across 50 update cycles.
|
|
135
|
+
const cycles = 50;
|
|
136
|
+
const perCycle = Math.ceil(stream.length / cycles);
|
|
137
|
+
for (let c = 0; c < cycles; c++) {
|
|
138
|
+
guard.beginUpdate();
|
|
139
|
+
for (let i = 0; i < perCycle; i++) {
|
|
140
|
+
const note = stream[c * perCycle + i];
|
|
141
|
+
if (note === undefined) break;
|
|
142
|
+
if (guard.accept(note)) accepted.push(note);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
expect(accepted).toEqual(["Concrete-but-repeated nit: x"]);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -137,7 +137,7 @@ export function deriveAdvisorTelemetry(
|
|
|
137
137
|
* inspect the workspace before weighing in. Names match the primary session's
|
|
138
138
|
* tool instances, which the advisor reuses.
|
|
139
139
|
*/
|
|
140
|
-
export const ADVISOR_READONLY_TOOL_NAMES: ReadonlySet<string> = new Set(["read", "
|
|
140
|
+
export const ADVISOR_READONLY_TOOL_NAMES: ReadonlySet<string> = new Set(["read", "grep", "glob"]);
|
|
141
141
|
|
|
142
142
|
function advisorNoteDedupeKey(note: string): string {
|
|
143
143
|
return note.trim().replace(/\s+/g, " ");
|