@oh-my-pi/pi-coding-agent 14.9.9 → 15.0.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 +123 -0
- package/examples/extensions/plan-mode.ts +0 -1
- package/package.json +9 -9
- package/scripts/build-binary.ts +5 -0
- package/scripts/format-prompts.ts +1 -1
- package/src/autoresearch/helpers.ts +17 -0
- package/src/autoresearch/tools/log-experiment.ts +9 -17
- package/src/autoresearch/tools/run-experiment.ts +2 -17
- package/src/capability/skill.ts +7 -0
- package/src/cli/args.ts +2 -2
- package/src/cli/list-models.ts +1 -1
- package/src/cli/shell-cli.ts +3 -13
- package/src/cli/update-cli.ts +1 -1
- package/src/cli.ts +11 -29
- package/src/commands/acp.ts +24 -0
- package/src/commands/launch.ts +6 -4
- package/src/commit/agentic/prompts/system.md +1 -1
- package/src/commit/agentic/tools/propose-changelog.ts +8 -1
- package/src/commit/analysis/conventional.ts +8 -66
- package/src/commit/map-reduce/reduce-phase.ts +6 -65
- package/src/commit/pipeline.ts +2 -2
- package/src/commit/shared-llm.ts +89 -0
- package/src/config/config-file.ts +210 -0
- package/src/config/model-equivalence.ts +8 -11
- package/src/config/model-registry.ts +13 -2
- package/src/config/model-resolver.ts +31 -4
- package/src/config/settings-schema.ts +102 -1
- package/src/config/settings.ts +1 -1
- package/src/config.ts +3 -219
- package/src/edit/index.ts +22 -1
- package/src/edit/modes/patch.ts +10 -0
- package/src/edit/modes/replace.ts +3 -0
- package/src/edit/renderer.ts +17 -1
- package/src/eval/js/context-manager.ts +1 -1
- package/src/eval/js/executor.ts +3 -0
- package/src/eval/js/shared/rewrite-imports.ts +122 -50
- package/src/eval/js/shared/runtime.ts +31 -4
- package/src/eval/js/tool-bridge.ts +43 -21
- package/src/eval/py/executor.ts +5 -0
- package/src/exa/factory.ts +2 -2
- package/src/exa/mcp-client.ts +74 -1
- package/src/exec/bash-executor.ts +5 -1
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +0 -11
- package/src/extensibility/extensions/runner.ts +55 -2
- package/src/extensibility/extensions/types.ts +98 -221
- package/src/extensibility/hooks/types.ts +89 -314
- package/src/extensibility/shared-events.ts +343 -0
- package/src/extensibility/skills.ts +42 -1
- package/src/goals/index.ts +3 -0
- package/src/goals/runtime.ts +500 -0
- package/src/goals/state.ts +37 -0
- package/src/goals/tools/goal-tool.ts +237 -0
- package/src/hashline/anchors.ts +2 -2
- package/src/hindsight/mental-models.ts +1 -1
- package/src/internal-urls/agent-protocol.ts +1 -20
- package/src/internal-urls/artifact-protocol.ts +1 -19
- package/src/internal-urls/docs-index.generated.ts +9 -10
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/issue-pr-protocol.ts +577 -0
- package/src/internal-urls/registry-helpers.ts +25 -0
- package/src/internal-urls/router.ts +6 -3
- package/src/internal-urls/types.ts +22 -1
- package/src/main.ts +24 -11
- package/src/mcp/oauth-flow.ts +20 -0
- package/src/modes/acp/acp-agent.ts +412 -71
- package/src/modes/acp/acp-client-bridge.ts +152 -0
- package/src/modes/acp/acp-event-mapper.ts +180 -15
- package/src/modes/acp/terminal-auth.ts +37 -0
- package/src/modes/components/assistant-message.ts +14 -8
- package/src/modes/components/bash-execution.ts +24 -63
- package/src/modes/components/custom-message.ts +14 -40
- package/src/modes/components/eval-execution.ts +27 -57
- package/src/modes/components/execution-shared.ts +102 -0
- package/src/modes/components/hook-message.ts +17 -49
- package/src/modes/components/mcp-add-wizard.ts +26 -5
- package/src/modes/components/message-frame.ts +88 -0
- package/src/modes/components/model-selector.ts +1 -1
- package/src/modes/components/read-tool-group.ts +29 -1
- package/src/modes/components/session-observer-overlay.ts +6 -2
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +55 -4
- package/src/modes/components/status-line/types.ts +4 -0
- package/src/modes/components/status-line.ts +28 -10
- package/src/modes/components/tool-execution.ts +7 -8
- package/src/modes/controllers/command-controller-shared.ts +108 -0
- package/src/modes/controllers/command-controller.ts +27 -10
- package/src/modes/controllers/event-controller.ts +60 -18
- package/src/modes/controllers/extension-ui-controller.ts +8 -2
- package/src/modes/controllers/input-controller.ts +85 -39
- package/src/modes/controllers/mcp-command-controller.ts +56 -61
- package/src/modes/controllers/ssh-command-controller.ts +18 -57
- package/src/modes/interactive-mode.ts +675 -39
- package/src/modes/print-mode.ts +16 -86
- package/src/modes/rpc/rpc-mode.ts +30 -88
- package/src/modes/runtime-init.ts +115 -0
- package/src/modes/theme/defaults/dark-poimandres.json +2 -0
- package/src/modes/theme/defaults/light-poimandres.json +2 -0
- package/src/modes/theme/theme.ts +18 -6
- package/src/modes/types.ts +20 -5
- package/src/modes/utils/context-usage.ts +13 -13
- package/src/modes/utils/ui-helpers.ts +25 -6
- package/src/plan-mode/approved-plan.ts +35 -1
- package/src/prompts/agents/designer.md +5 -5
- package/src/prompts/agents/explore.md +7 -7
- package/src/prompts/agents/init.md +9 -9
- package/src/prompts/agents/librarian.md +14 -14
- package/src/prompts/agents/plan.md +4 -4
- package/src/prompts/agents/reviewer.md +5 -5
- package/src/prompts/agents/task.md +10 -10
- package/src/prompts/commands/orchestrate.md +2 -2
- package/src/prompts/compaction/branch-summary.md +3 -3
- package/src/prompts/compaction/compaction-short-summary.md +7 -7
- package/src/prompts/compaction/compaction-summary-context.md +1 -1
- package/src/prompts/compaction/compaction-summary.md +5 -5
- package/src/prompts/compaction/compaction-turn-prefix.md +3 -3
- package/src/prompts/compaction/compaction-update-summary.md +11 -11
- package/src/prompts/goals/goal-budget-limit.md +16 -0
- package/src/prompts/goals/goal-continuation.md +28 -0
- package/src/prompts/goals/goal-mode-active.md +23 -0
- package/src/prompts/memories/consolidation.md +2 -2
- package/src/prompts/memories/read-path.md +1 -1
- package/src/prompts/memories/stage_one_input.md +1 -1
- package/src/prompts/memories/stage_one_system.md +5 -5
- package/src/prompts/review-request.md +4 -4
- package/src/prompts/system/agent-creation-architect.md +17 -17
- package/src/prompts/system/agent-creation-user.md +2 -2
- package/src/prompts/system/commit-message-system.md +2 -2
- package/src/prompts/system/custom-system-prompt.md +2 -2
- package/src/prompts/system/eager-todo.md +6 -6
- package/src/prompts/system/handoff-document.md +1 -1
- package/src/prompts/system/plan-mode-active.md +25 -24
- package/src/prompts/system/plan-mode-approved.md +4 -4
- package/src/prompts/system/plan-mode-compact-instructions.md +16 -0
- package/src/prompts/system/plan-mode-reference.md +2 -2
- package/src/prompts/system/plan-mode-subagent.md +8 -8
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +3 -3
- package/src/prompts/system/project-prompt.md +4 -4
- package/src/prompts/system/subagent-system-prompt.md +7 -7
- package/src/prompts/system/subagent-yield-reminder.md +4 -4
- package/src/prompts/system/system-prompt.md +72 -71
- package/src/prompts/system/ttsr-interrupt.md +1 -1
- package/src/prompts/tools/apply-patch.md +1 -1
- package/src/prompts/tools/ast-edit.md +3 -3
- package/src/prompts/tools/ast-grep.md +3 -3
- package/src/prompts/tools/bash.md +6 -0
- package/src/prompts/tools/browser.md +3 -3
- package/src/prompts/tools/checkpoint.md +3 -3
- package/src/prompts/tools/find.md +3 -3
- package/src/prompts/tools/github.md +2 -5
- package/src/prompts/tools/goal.md +13 -0
- package/src/prompts/tools/hashline.md +104 -116
- package/src/prompts/tools/image-gen.md +3 -3
- package/src/prompts/tools/irc.md +1 -1
- package/src/prompts/tools/lsp.md +2 -2
- package/src/prompts/tools/patch.md +6 -6
- package/src/prompts/tools/read.md +8 -7
- package/src/prompts/tools/replace.md +5 -5
- package/src/prompts/tools/resolve.md +6 -5
- package/src/prompts/tools/retain.md +1 -1
- package/src/prompts/tools/rewind.md +2 -2
- package/src/prompts/tools/search.md +2 -2
- package/src/prompts/tools/ssh.md +2 -2
- package/src/prompts/tools/task.md +12 -6
- package/src/prompts/tools/web-search.md +2 -2
- package/src/prompts/tools/write.md +3 -3
- package/src/sdk.ts +81 -17
- package/src/session/agent-session.ts +656 -125
- package/src/session/blob-store.ts +36 -3
- package/src/session/client-bridge.ts +81 -0
- package/src/session/compaction/errors.ts +31 -0
- package/src/session/compaction/index.ts +1 -0
- package/src/session/messages.ts +67 -2
- package/src/session/session-manager.ts +131 -12
- package/src/session/session-storage.ts +33 -15
- package/src/session/streaming-output.ts +309 -13
- package/src/slash-commands/acp-builtins.ts +46 -0
- package/src/slash-commands/builtin-registry.ts +717 -116
- package/src/slash-commands/helpers/context-report.ts +39 -0
- package/src/slash-commands/helpers/format.ts +23 -0
- package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
- package/src/slash-commands/helpers/mcp.ts +532 -0
- package/src/slash-commands/helpers/parse.ts +85 -0
- package/src/slash-commands/helpers/ssh.ts +193 -0
- package/src/slash-commands/helpers/todo.ts +279 -0
- package/src/slash-commands/helpers/usage-report.ts +91 -0
- package/src/slash-commands/types.ts +126 -0
- package/src/ssh/ssh-executor.ts +5 -0
- package/src/system-prompt.ts +4 -2
- package/src/task/executor.ts +27 -10
- package/src/task/index.ts +20 -1
- package/src/task/render.ts +27 -18
- package/src/task/types.ts +4 -0
- package/src/tools/ast-edit.ts +21 -120
- package/src/tools/ast-grep.ts +21 -119
- package/src/tools/bash-interactive.ts +9 -1
- package/src/tools/bash.ts +203 -6
- package/src/tools/browser/attach.ts +3 -3
- package/src/tools/browser/launch.ts +81 -18
- package/src/tools/browser/registry.ts +1 -5
- package/src/tools/browser/tab-supervisor.ts +51 -14
- package/src/tools/conflict-detect.ts +21 -10
- package/src/tools/eval.ts +3 -1
- package/src/tools/fetch.ts +15 -4
- package/src/tools/find.ts +39 -39
- package/src/tools/gh-renderer.ts +0 -12
- package/src/tools/gh.ts +689 -182
- package/src/tools/github-cache.ts +548 -0
- package/src/tools/index.ts +25 -11
- package/src/tools/inspect-image.ts +3 -10
- package/src/tools/output-meta.ts +176 -37
- package/src/tools/path-utils.ts +125 -2
- package/src/tools/read.ts +605 -239
- package/src/tools/render-utils.ts +92 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +72 -44
- package/src/tools/search.ts +120 -186
- package/src/tools/write.ts +67 -10
- package/src/tui/code-cell.ts +70 -2
- package/src/utils/file-mentions.ts +1 -1
- package/src/utils/image-loading.ts +7 -3
- package/src/utils/image-resize.ts +32 -43
- package/src/vim/parser.ts +0 -17
- package/src/vim/render.ts +1 -1
- package/src/vim/types.ts +1 -1
- package/src/web/search/providers/gemini.ts +35 -95
- package/src/prompts/tools/exit-plan-mode.md +0 -6
- package/src/tools/exit-plan-mode.ts +0 -97
- package/src/utils/fuzzy.ts +0 -108
- package/src/utils/image-convert.ts +0 -27
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite-backed cache for rendered `github` issue/PR view output, plus a
|
|
3
|
+
* generic cache-aware wrapper that the tool ops and the `issue://`/`pr://`
|
|
4
|
+
* protocol handlers share.
|
|
5
|
+
*
|
|
6
|
+
* Storage:
|
|
7
|
+
* One process-wide connection opens lazily on first hit and stays open. All
|
|
8
|
+
* helpers swallow open/IO failures and degrade to "no cache" so a corrupt or
|
|
9
|
+
* unreadable DB never blocks a `gh` call.
|
|
10
|
+
*
|
|
11
|
+
* TTL:
|
|
12
|
+
* Soft TTL → return cached row directly.
|
|
13
|
+
* Past soft TTL but within hard TTL → return cached row AND schedule a
|
|
14
|
+
* background refresh (errors logged, never thrown).
|
|
15
|
+
* Past hard TTL → treat as miss and fetch fresh.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Database } from "bun:sqlite";
|
|
19
|
+
import * as fs from "node:fs";
|
|
20
|
+
import * as os from "node:os";
|
|
21
|
+
import * as path from "node:path";
|
|
22
|
+
import { getGithubCacheDbPath, logger } from "@oh-my-pi/pi-utils";
|
|
23
|
+
import type { Settings } from "../config/settings";
|
|
24
|
+
|
|
25
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
// Storage layer
|
|
27
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
export type CacheKind = "issue" | "pr" | "pr-diff";
|
|
30
|
+
|
|
31
|
+
const DEFAULT_CACHE_AUTH_KEY = "default";
|
|
32
|
+
|
|
33
|
+
export interface CachedView<T = unknown> {
|
|
34
|
+
authKey: string;
|
|
35
|
+
repo: string;
|
|
36
|
+
kind: CacheKind;
|
|
37
|
+
number: number;
|
|
38
|
+
includeComments: boolean;
|
|
39
|
+
fetchedAt: number;
|
|
40
|
+
payload: T;
|
|
41
|
+
rendered: string;
|
|
42
|
+
sourceUrl: string | undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface Row {
|
|
46
|
+
auth_key: string;
|
|
47
|
+
repo: string;
|
|
48
|
+
kind: CacheKind;
|
|
49
|
+
number: number;
|
|
50
|
+
include_comments: number;
|
|
51
|
+
fetched_at: number;
|
|
52
|
+
payload: string;
|
|
53
|
+
rendered: string;
|
|
54
|
+
source_url: string | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const DEFAULT_SOFT_TTL_SEC = 300; // 5 minutes
|
|
58
|
+
const DEFAULT_HARD_TTL_SEC = 60 * 60 * 24 * 7; // 7 days
|
|
59
|
+
|
|
60
|
+
let cachedDb: Database | null = null;
|
|
61
|
+
let openAttempted = false;
|
|
62
|
+
|
|
63
|
+
function ensureParentDir(filePath: string): void {
|
|
64
|
+
try {
|
|
65
|
+
const dir = path.dirname(filePath);
|
|
66
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
67
|
+
} catch (err) {
|
|
68
|
+
logger.debug("github cache: failed to create private parent dir", { err: String(err) });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function chmodIfExists(filePath: string, mode: number): void {
|
|
73
|
+
try {
|
|
74
|
+
fs.chmodSync(filePath, mode);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
77
|
+
logger.debug("github cache: chmod failed", { err: String(err), path: filePath });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function protectDbFiles(dbPath: string): void {
|
|
83
|
+
chmodIfExists(dbPath, 0o600);
|
|
84
|
+
chmodIfExists(`${dbPath}-wal`, 0o600);
|
|
85
|
+
chmodIfExists(`${dbPath}-shm`, 0o600);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function openDb(): Database | null {
|
|
89
|
+
if (cachedDb) return cachedDb;
|
|
90
|
+
if (openAttempted) return null;
|
|
91
|
+
openAttempted = true;
|
|
92
|
+
try {
|
|
93
|
+
const dbPath = getGithubCacheDbPath();
|
|
94
|
+
ensureParentDir(dbPath);
|
|
95
|
+
const db = new Database(dbPath);
|
|
96
|
+
db.run(`
|
|
97
|
+
PRAGMA journal_mode=WAL;
|
|
98
|
+
PRAGMA synchronous=NORMAL;
|
|
99
|
+
PRAGMA busy_timeout=5000;
|
|
100
|
+
`);
|
|
101
|
+
// Migrate any pre-existing table whose key/check constraint predates
|
|
102
|
+
// the current schema. The cache is regenerable, so we drop rows rather
|
|
103
|
+
// than running an in-place ALTER dance.
|
|
104
|
+
const userVersion = (db.prepare("PRAGMA user_version").get() as { user_version?: number } | undefined)
|
|
105
|
+
?.user_version;
|
|
106
|
+
if (userVersion !== undefined && userVersion < 3) {
|
|
107
|
+
db.run("DROP TABLE IF EXISTS github_view_cache");
|
|
108
|
+
}
|
|
109
|
+
db.run(`
|
|
110
|
+
CREATE TABLE IF NOT EXISTS github_view_cache (
|
|
111
|
+
auth_key TEXT NOT NULL,
|
|
112
|
+
repo TEXT NOT NULL,
|
|
113
|
+
kind TEXT NOT NULL CHECK (kind IN ('issue','pr','pr-diff')),
|
|
114
|
+
number INTEGER NOT NULL,
|
|
115
|
+
include_comments INTEGER NOT NULL,
|
|
116
|
+
fetched_at INTEGER NOT NULL,
|
|
117
|
+
payload TEXT NOT NULL,
|
|
118
|
+
rendered TEXT NOT NULL,
|
|
119
|
+
source_url TEXT,
|
|
120
|
+
PRIMARY KEY (auth_key, repo, kind, number, include_comments)
|
|
121
|
+
);
|
|
122
|
+
CREATE INDEX IF NOT EXISTS idx_github_view_cache_fetched ON github_view_cache(fetched_at);
|
|
123
|
+
PRAGMA user_version = 3;
|
|
124
|
+
`);
|
|
125
|
+
protectDbFiles(dbPath);
|
|
126
|
+
cachedDb = db;
|
|
127
|
+
// No eviction on open: the default `DEFAULT_HARD_TTL_SEC` is a coarse
|
|
128
|
+
// backstop that runs before user settings load, so applying it here
|
|
129
|
+
// would nuke rows still valid under a stricter-or-laxer configured
|
|
130
|
+
// `github.cache.hardTtlSec`. The per-lookup `sweepIfDue()` in
|
|
131
|
+
// `getOrFetchView()` enforces the *configured* retention instead.
|
|
132
|
+
return db;
|
|
133
|
+
} catch (err) {
|
|
134
|
+
logger.warn("github cache: failed to open DB; cache disabled", { err: String(err) });
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function evictExpired(db: Database, hardTtlMs: number): void {
|
|
140
|
+
try {
|
|
141
|
+
const cutoff = Date.now() - hardTtlMs;
|
|
142
|
+
db.prepare("DELETE FROM github_view_cache WHERE fetched_at < ?").run(cutoff);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
logger.debug("github cache: eviction failed", { err: String(err) });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Throttle for the per-lookup configured-TTL sweep. We don't want every
|
|
150
|
+
* cached read to issue a DELETE; once per `SWEEP_INTERVAL_MS` is enough to
|
|
151
|
+
* cap the on-disk exposure window at roughly `hardTtlMs + SWEEP_INTERVAL_MS`.
|
|
152
|
+
*/
|
|
153
|
+
const SWEEP_INTERVAL_MS = 60_000;
|
|
154
|
+
let lastSweepAt = 0;
|
|
155
|
+
|
|
156
|
+
function sweepIfDue(hardTtlMs: number): void {
|
|
157
|
+
const now = Date.now();
|
|
158
|
+
if (now - lastSweepAt < SWEEP_INTERVAL_MS) return;
|
|
159
|
+
const db = openDb();
|
|
160
|
+
if (!db) return;
|
|
161
|
+
lastSweepAt = now;
|
|
162
|
+
evictExpired(db, hardTtlMs);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function getGhConfigDir(): string {
|
|
166
|
+
const override = process.env.GH_CONFIG_DIR;
|
|
167
|
+
if (override) return override;
|
|
168
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
169
|
+
if (xdg) return path.join(xdg, "gh");
|
|
170
|
+
return path.join(os.homedir(), ".config", "gh");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function hashCacheIdentity(parts: string[]): string {
|
|
174
|
+
return Bun.hash(parts.map(part => `${part.length}:${part}`).join("|")).toString(36);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Best-effort local fingerprint for the active GitHub CLI credentials.
|
|
179
|
+
*
|
|
180
|
+
* Cache hits must not cross account/token boundaries, but doing a `gh api user`
|
|
181
|
+
* probe before every cached read would defeat the soft-TTL contract that cache
|
|
182
|
+
* hits avoid a gh round-trip. Instead, key rows by credential material that the
|
|
183
|
+
* GitHub CLI itself consumes: token environment variables and/or hosts.yml.
|
|
184
|
+
* The DB stores only a hash, never the token or hosts.yml contents. If no
|
|
185
|
+
* credential source is visible, callers should pass `null` to bypass caching.
|
|
186
|
+
*/
|
|
187
|
+
export function resolveGithubCacheAuthKey(host: string = process.env.GH_HOST || "github.com"): string | undefined {
|
|
188
|
+
const parts: string[] = [`host:${host}`];
|
|
189
|
+
let hasCredentialMaterial = false;
|
|
190
|
+
for (const name of ["GH_TOKEN", "GITHUB_TOKEN", "GH_ENTERPRISE_TOKEN", "GITHUB_ENTERPRISE_TOKEN"]) {
|
|
191
|
+
const value = process.env[name];
|
|
192
|
+
if (!value) continue;
|
|
193
|
+
hasCredentialMaterial = true;
|
|
194
|
+
parts.push(`${name}:${value}`);
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const hostsPath = path.join(getGhConfigDir(), "hosts.yml");
|
|
198
|
+
const hosts = fs.readFileSync(hostsPath, "utf8");
|
|
199
|
+
hasCredentialMaterial = true;
|
|
200
|
+
parts.push(`hosts:${hosts}`);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
203
|
+
logger.debug("github cache: failed to read gh hosts config for cache identity", { err: String(err) });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (!hasCredentialMaterial) return undefined;
|
|
207
|
+
return `${host}:${hashCacheIdentity(parts)}`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function normalizeRepo(repo: string): string {
|
|
211
|
+
return repo.toLowerCase();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function getCached<T = unknown>(
|
|
215
|
+
repo: string,
|
|
216
|
+
kind: CacheKind,
|
|
217
|
+
number: number,
|
|
218
|
+
includeComments: boolean,
|
|
219
|
+
authKey: string = DEFAULT_CACHE_AUTH_KEY,
|
|
220
|
+
): CachedView<T> | null {
|
|
221
|
+
const db = openDb();
|
|
222
|
+
if (!db) return null;
|
|
223
|
+
try {
|
|
224
|
+
const row = db
|
|
225
|
+
.prepare(
|
|
226
|
+
"SELECT auth_key, repo, kind, number, include_comments, fetched_at, payload, rendered, source_url FROM github_view_cache WHERE auth_key = ? AND repo = ? AND kind = ? AND number = ? AND include_comments = ?",
|
|
227
|
+
)
|
|
228
|
+
.get(authKey, normalizeRepo(repo), kind, number, includeComments ? 1 : 0) as Row | undefined;
|
|
229
|
+
if (!row) return null;
|
|
230
|
+
let payload: T;
|
|
231
|
+
try {
|
|
232
|
+
payload = JSON.parse(row.payload) as T;
|
|
233
|
+
} catch (err) {
|
|
234
|
+
logger.debug("github cache: corrupt payload row, ignoring", { err: String(err), repo, kind, number });
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
authKey: row.auth_key,
|
|
239
|
+
repo: row.repo,
|
|
240
|
+
kind: row.kind,
|
|
241
|
+
number: row.number,
|
|
242
|
+
includeComments: row.include_comments === 1,
|
|
243
|
+
fetchedAt: row.fetched_at,
|
|
244
|
+
payload,
|
|
245
|
+
rendered: row.rendered,
|
|
246
|
+
sourceUrl: row.source_url ?? undefined,
|
|
247
|
+
};
|
|
248
|
+
} catch (err) {
|
|
249
|
+
logger.debug("github cache: read failed", { err: String(err) });
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export interface PutCachedInput<T = unknown> {
|
|
255
|
+
authKey?: string;
|
|
256
|
+
repo: string;
|
|
257
|
+
kind: CacheKind;
|
|
258
|
+
number: number;
|
|
259
|
+
includeComments: boolean;
|
|
260
|
+
payload: T;
|
|
261
|
+
rendered: string;
|
|
262
|
+
sourceUrl?: string;
|
|
263
|
+
fetchedAt?: number;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function putCached<T = unknown>(input: PutCachedInput<T>): void {
|
|
267
|
+
const db = openDb();
|
|
268
|
+
if (!db) return;
|
|
269
|
+
try {
|
|
270
|
+
const fetchedAt = input.fetchedAt ?? Date.now();
|
|
271
|
+
const payloadJson = JSON.stringify(input.payload);
|
|
272
|
+
db.prepare(
|
|
273
|
+
"INSERT OR REPLACE INTO github_view_cache (auth_key, repo, kind, number, include_comments, fetched_at, payload, rendered, source_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
274
|
+
).run(
|
|
275
|
+
input.authKey ?? DEFAULT_CACHE_AUTH_KEY,
|
|
276
|
+
normalizeRepo(input.repo),
|
|
277
|
+
input.kind,
|
|
278
|
+
input.number,
|
|
279
|
+
input.includeComments ? 1 : 0,
|
|
280
|
+
fetchedAt,
|
|
281
|
+
payloadJson,
|
|
282
|
+
input.rendered,
|
|
283
|
+
input.sourceUrl ?? null,
|
|
284
|
+
);
|
|
285
|
+
protectDbFiles(getGithubCacheDbPath());
|
|
286
|
+
} catch (err) {
|
|
287
|
+
logger.debug("github cache: write failed", { err: String(err) });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Drop a specific cache entry. */
|
|
292
|
+
export function invalidate(
|
|
293
|
+
repo: string,
|
|
294
|
+
kind: CacheKind,
|
|
295
|
+
number: number,
|
|
296
|
+
includeComments?: boolean,
|
|
297
|
+
authKey: string = DEFAULT_CACHE_AUTH_KEY,
|
|
298
|
+
): void {
|
|
299
|
+
const db = openDb();
|
|
300
|
+
if (!db) return;
|
|
301
|
+
try {
|
|
302
|
+
if (includeComments === undefined) {
|
|
303
|
+
db.prepare("DELETE FROM github_view_cache WHERE auth_key = ? AND repo = ? AND kind = ? AND number = ?").run(
|
|
304
|
+
authKey,
|
|
305
|
+
normalizeRepo(repo),
|
|
306
|
+
kind,
|
|
307
|
+
number,
|
|
308
|
+
);
|
|
309
|
+
} else {
|
|
310
|
+
db.prepare(
|
|
311
|
+
"DELETE FROM github_view_cache WHERE auth_key = ? AND repo = ? AND kind = ? AND number = ? AND include_comments = ?",
|
|
312
|
+
).run(authKey, normalizeRepo(repo), kind, number, includeComments ? 1 : 0);
|
|
313
|
+
}
|
|
314
|
+
} catch (err) {
|
|
315
|
+
logger.debug("github cache: invalidate failed", { err: String(err) });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Drop every cached row. Test helper. */
|
|
320
|
+
export function clearAll(): void {
|
|
321
|
+
const db = openDb();
|
|
322
|
+
if (!db) return;
|
|
323
|
+
try {
|
|
324
|
+
db.prepare("DELETE FROM github_view_cache").run();
|
|
325
|
+
} catch (err) {
|
|
326
|
+
logger.debug("github cache: clear failed", { err: String(err) });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Test/maintenance helper. Closes and forgets the cached connection so the
|
|
332
|
+
* next access reopens against (possibly) a different DB path.
|
|
333
|
+
*/
|
|
334
|
+
export function resetForTests(): void {
|
|
335
|
+
if (cachedDb) {
|
|
336
|
+
try {
|
|
337
|
+
cachedDb.close();
|
|
338
|
+
} catch {
|
|
339
|
+
// Closing failures are non-fatal.
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
cachedDb = null;
|
|
343
|
+
openAttempted = false;
|
|
344
|
+
lastSweepAt = 0;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
348
|
+
// Cache-aware lookup wrapper
|
|
349
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
350
|
+
|
|
351
|
+
export interface FreshResult<T> {
|
|
352
|
+
rendered: string;
|
|
353
|
+
sourceUrl: string | undefined;
|
|
354
|
+
payload: T;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export interface CacheLookupOptions<T> {
|
|
358
|
+
repo: string;
|
|
359
|
+
kind: CacheKind;
|
|
360
|
+
number: number;
|
|
361
|
+
includeComments: boolean;
|
|
362
|
+
/**
|
|
363
|
+
* Auth/credential namespace for cache rows. Omit only in storage-layer
|
|
364
|
+
* tests; pass `null` when production code cannot determine an identity and
|
|
365
|
+
* must bypass persistent cache reads/writes.
|
|
366
|
+
*/
|
|
367
|
+
authKey?: string | null;
|
|
368
|
+
fetchFresh: () => Promise<FreshResult<T>>;
|
|
369
|
+
settings?: Settings | undefined;
|
|
370
|
+
now?: number;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export type CacheStatus = "miss" | "fresh" | "stale" | "disabled";
|
|
374
|
+
|
|
375
|
+
export interface CacheLookupResult<T> {
|
|
376
|
+
rendered: string;
|
|
377
|
+
sourceUrl: string | undefined;
|
|
378
|
+
payload: T;
|
|
379
|
+
status: CacheStatus;
|
|
380
|
+
fetchedAt: number;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function readNumberSetting(settings: Settings | undefined, key: string, fallback: number): number {
|
|
384
|
+
if (!settings) return fallback;
|
|
385
|
+
try {
|
|
386
|
+
const value = (settings as unknown as { get(k: string): unknown }).get(key);
|
|
387
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
|
|
388
|
+
} catch {
|
|
389
|
+
// Unknown setting paths fall through to default; settings may be a
|
|
390
|
+
// stripped test stub that doesn't expose every key.
|
|
391
|
+
}
|
|
392
|
+
return fallback;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function readBooleanSetting(settings: Settings | undefined, key: string, fallback: boolean): boolean {
|
|
396
|
+
if (!settings) return fallback;
|
|
397
|
+
try {
|
|
398
|
+
const value = (settings as unknown as { get(k: string): unknown }).get(key);
|
|
399
|
+
if (typeof value === "boolean") return value;
|
|
400
|
+
} catch {
|
|
401
|
+
// Same fallback rationale as readNumberSetting.
|
|
402
|
+
}
|
|
403
|
+
return fallback;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export interface CacheTtl {
|
|
407
|
+
softMs: number;
|
|
408
|
+
hardMs: number;
|
|
409
|
+
enabled: boolean;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function resolveCacheTtl(settings?: Settings): CacheTtl {
|
|
413
|
+
const softSec = readNumberSetting(settings, "github.cache.softTtlSec", DEFAULT_SOFT_TTL_SEC);
|
|
414
|
+
const hardSec = readNumberSetting(settings, "github.cache.hardTtlSec", DEFAULT_HARD_TTL_SEC);
|
|
415
|
+
const enabled = readBooleanSetting(settings, "github.cache.enabled", true);
|
|
416
|
+
return {
|
|
417
|
+
softMs: Math.max(0, softSec) * 1000,
|
|
418
|
+
hardMs: Math.max(0, hardSec) * 1000,
|
|
419
|
+
enabled,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function storeResult<T>(
|
|
424
|
+
authKey: string,
|
|
425
|
+
repo: string,
|
|
426
|
+
kind: CacheKind,
|
|
427
|
+
number: number,
|
|
428
|
+
includeComments: boolean,
|
|
429
|
+
result: FreshResult<T>,
|
|
430
|
+
fetchedAt: number,
|
|
431
|
+
): void {
|
|
432
|
+
putCached<T>({
|
|
433
|
+
authKey,
|
|
434
|
+
repo,
|
|
435
|
+
kind,
|
|
436
|
+
number,
|
|
437
|
+
includeComments,
|
|
438
|
+
payload: result.payload,
|
|
439
|
+
rendered: result.rendered,
|
|
440
|
+
sourceUrl: result.sourceUrl,
|
|
441
|
+
fetchedAt,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function scheduleBackgroundRefresh<T>(
|
|
446
|
+
authKey: string,
|
|
447
|
+
repo: string,
|
|
448
|
+
kind: CacheKind,
|
|
449
|
+
number: number,
|
|
450
|
+
includeComments: boolean,
|
|
451
|
+
fetchFresh: () => Promise<FreshResult<T>>,
|
|
452
|
+
): void {
|
|
453
|
+
queueMicrotask(() => {
|
|
454
|
+
const promise = fetchFresh();
|
|
455
|
+
promise
|
|
456
|
+
.then(fresh => {
|
|
457
|
+
storeResult(authKey, repo, kind, number, includeComments, fresh, Date.now());
|
|
458
|
+
})
|
|
459
|
+
.catch(err => {
|
|
460
|
+
logger.debug("github cache: background refresh failed", {
|
|
461
|
+
err: String(err),
|
|
462
|
+
repo,
|
|
463
|
+
kind,
|
|
464
|
+
number,
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export async function getOrFetchView<T>(options: CacheLookupOptions<T>): Promise<CacheLookupResult<T>> {
|
|
471
|
+
const ttl = resolveCacheTtl(options.settings);
|
|
472
|
+
const now = options.now ?? Date.now();
|
|
473
|
+
const authKey = options.authKey === undefined ? DEFAULT_CACHE_AUTH_KEY : options.authKey;
|
|
474
|
+
|
|
475
|
+
if (!ttl.enabled || authKey === null) {
|
|
476
|
+
const fresh = await options.fetchFresh();
|
|
477
|
+
return { ...fresh, status: "disabled", fetchedAt: now };
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Enforce the *configured* hard TTL against on-disk rows. This is what
|
|
481
|
+
// makes `github.cache.hardTtlSec` a real retention cap rather than a soft
|
|
482
|
+
// suggestion the next `openDb()` call eventually honors.
|
|
483
|
+
sweepIfDue(ttl.hardMs);
|
|
484
|
+
|
|
485
|
+
const cached: CachedView<T> | null = getCached<T>(
|
|
486
|
+
options.repo,
|
|
487
|
+
options.kind,
|
|
488
|
+
options.number,
|
|
489
|
+
options.includeComments,
|
|
490
|
+
authKey,
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
if (cached) {
|
|
494
|
+
const age = now - cached.fetchedAt;
|
|
495
|
+
if (age > ttl.hardMs) {
|
|
496
|
+
// Past hard TTL: drop the row eagerly so the on-disk exposure window
|
|
497
|
+
// is bounded even if `fetchFresh()` then fails (network down, gh
|
|
498
|
+
// auth lapse, etc.) and we never get to overwrite it.
|
|
499
|
+
invalidate(options.repo, options.kind, options.number, options.includeComments, authKey);
|
|
500
|
+
} else if (age <= ttl.softMs) {
|
|
501
|
+
return {
|
|
502
|
+
rendered: cached.rendered,
|
|
503
|
+
sourceUrl: cached.sourceUrl,
|
|
504
|
+
payload: cached.payload,
|
|
505
|
+
status: "fresh",
|
|
506
|
+
fetchedAt: cached.fetchedAt,
|
|
507
|
+
};
|
|
508
|
+
} else {
|
|
509
|
+
scheduleBackgroundRefresh(
|
|
510
|
+
authKey,
|
|
511
|
+
options.repo,
|
|
512
|
+
options.kind,
|
|
513
|
+
options.number,
|
|
514
|
+
options.includeComments,
|
|
515
|
+
options.fetchFresh,
|
|
516
|
+
);
|
|
517
|
+
return {
|
|
518
|
+
rendered: cached.rendered,
|
|
519
|
+
sourceUrl: cached.sourceUrl,
|
|
520
|
+
payload: cached.payload,
|
|
521
|
+
status: "stale",
|
|
522
|
+
fetchedAt: cached.fetchedAt,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const fresh = await options.fetchFresh();
|
|
528
|
+
const fetchedAt = Date.now();
|
|
529
|
+
storeResult(authKey, options.repo, options.kind, options.number, options.includeComments, fresh, fetchedAt);
|
|
530
|
+
return { ...fresh, status: "miss", fetchedAt };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Human-friendly freshness note for protocol-handler `notes[]` rendering.
|
|
535
|
+
*/
|
|
536
|
+
export function formatFreshnessNote(status: CacheStatus, fetchedAtMs: number, now: number = Date.now()): string {
|
|
537
|
+
if (status === "miss") return "Fetched live";
|
|
538
|
+
if (status === "disabled") return "Cache disabled; fetched live";
|
|
539
|
+
const ageSec = Math.max(0, Math.round((now - fetchedAtMs) / 1000));
|
|
540
|
+
const human =
|
|
541
|
+
ageSec < 60
|
|
542
|
+
? `${ageSec}s ago`
|
|
543
|
+
: ageSec < 3600
|
|
544
|
+
? `${Math.round(ageSec / 60)}m ago`
|
|
545
|
+
: `${Math.round(ageSec / 3600)}h ago`;
|
|
546
|
+
if (status === "stale") return `Cached: ${human} (refreshing in background)`;
|
|
547
|
+
return `Cached: ${human}`;
|
|
548
|
+
}
|
package/src/tools/index.ts
CHANGED
|
@@ -6,11 +6,14 @@ import type { Settings } from "../config/settings";
|
|
|
6
6
|
import { EditTool } from "../edit";
|
|
7
7
|
import { checkPythonKernelAvailability } from "../eval/py/kernel";
|
|
8
8
|
import type { Skill } from "../extensibility/skills";
|
|
9
|
+
import type { GoalModeState, GoalRuntime } from "../goals";
|
|
10
|
+
import { GoalTool } from "../goals/tools/goal-tool";
|
|
9
11
|
import type { HindsightSessionState } from "../hindsight/state";
|
|
10
12
|
import { LspTool } from "../lsp";
|
|
11
13
|
import type { PlanModeState } from "../plan-mode/state";
|
|
12
14
|
import { type AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
|
|
13
15
|
import type { ArtifactManager } from "../session/artifacts";
|
|
16
|
+
import type { ClientBridge } from "../session/client-bridge";
|
|
14
17
|
import type { CustomMessage } from "../session/messages";
|
|
15
18
|
import type { ToolChoiceQueue } from "../session/tool-choice-queue";
|
|
16
19
|
import { TaskTool } from "../task";
|
|
@@ -28,7 +31,6 @@ import { CalculatorTool } from "./calculator";
|
|
|
28
31
|
import { type CheckpointState, CheckpointTool, RewindTool } from "./checkpoint";
|
|
29
32
|
import { DebugTool } from "./debug";
|
|
30
33
|
import { EvalTool } from "./eval";
|
|
31
|
-
import { ExitPlanModeTool } from "./exit-plan-mode";
|
|
32
34
|
import { FindTool } from "./find";
|
|
33
35
|
import { GithubTool } from "./gh";
|
|
34
36
|
import { HindsightRecallTool } from "./hindsight-recall";
|
|
@@ -56,6 +58,7 @@ import { YieldTool } from "./yield";
|
|
|
56
58
|
export * from "../edit";
|
|
57
59
|
export * from "../exa";
|
|
58
60
|
export type * from "../exa/types";
|
|
61
|
+
export * from "../goals";
|
|
59
62
|
export * from "../lsp";
|
|
60
63
|
export * from "../session/streaming-output";
|
|
61
64
|
export * from "../task";
|
|
@@ -69,7 +72,6 @@ export * from "./calculator";
|
|
|
69
72
|
export * from "./checkpoint";
|
|
70
73
|
export * from "./debug";
|
|
71
74
|
export * from "./eval";
|
|
72
|
-
export * from "./exit-plan-mode";
|
|
73
75
|
export * from "./find";
|
|
74
76
|
export * from "./gh";
|
|
75
77
|
export * from "./hindsight-recall";
|
|
@@ -178,6 +180,12 @@ export interface ToolSession {
|
|
|
178
180
|
settings: Settings;
|
|
179
181
|
/** Plan mode state (if active) */
|
|
180
182
|
getPlanModeState?: () => PlanModeState | undefined;
|
|
183
|
+
/** Goal mode state (if active or paused) */
|
|
184
|
+
getGoalModeState?: () => GoalModeState | undefined;
|
|
185
|
+
/** Goal runtime for the active agent session. */
|
|
186
|
+
getGoalRuntime?: () => GoalRuntime | undefined;
|
|
187
|
+
/** Bridge to the connected client (e.g. ACP editor host). Tools should route fs/terminal/permission requests through this when available. */
|
|
188
|
+
getClientBridge?: () => ClientBridge | undefined;
|
|
181
189
|
/** Get compact conversation context for subagents (excludes tool results, system prompts) */
|
|
182
190
|
getCompactContext?: () => string;
|
|
183
191
|
/** Get cached todo phases for this session. */
|
|
@@ -217,6 +225,12 @@ export interface ToolSession {
|
|
|
217
225
|
steer?(message: { customType: string; content: string; details?: unknown }): void;
|
|
218
226
|
/** Peek the currently in-flight tool-choice queue directive's invocation handler. Used by the `resolve` tool to dispatch to the pending action. */
|
|
219
227
|
peekQueueInvoker?(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
|
|
228
|
+
/** Peek the long-lived "standing" resolve handler registered by a mode (e.g. plan mode).
|
|
229
|
+
* Consulted by the `resolve` tool as a fallback when no queue invoker is in flight,
|
|
230
|
+
* letting modes accept `resolve` invocations without forcing the tool choice every turn. */
|
|
231
|
+
peekStandingResolveHandler?(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
|
|
232
|
+
/** Register or clear the standing resolve handler. Passing `null` clears it. */
|
|
233
|
+
setStandingResolveHandler?(handler: ((input: unknown) => Promise<unknown> | unknown) | null): void;
|
|
220
234
|
/** Get active checkpoint state if any. */
|
|
221
235
|
getCheckpointState?: () => CheckpointState | undefined;
|
|
222
236
|
/** Set or clear active checkpoint state. */
|
|
@@ -300,8 +314,8 @@ export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
|
|
|
300
314
|
yield: s => new YieldTool(s),
|
|
301
315
|
report_finding: () => reportFindingTool,
|
|
302
316
|
report_tool_issue: s => createReportToolIssueTool(s),
|
|
303
|
-
exit_plan_mode: s => new ExitPlanModeTool(s),
|
|
304
317
|
resolve: s => new ResolveTool(s),
|
|
318
|
+
goal: s => new GoalTool(s),
|
|
305
319
|
};
|
|
306
320
|
|
|
307
321
|
export type ToolName = keyof typeof BUILTIN_TOOLS;
|
|
@@ -348,11 +362,12 @@ export function resolveEvalBackends(session: ToolSession): EvalBackendsAllowance
|
|
|
348
362
|
export async function createTools(session: ToolSession, toolNames?: string[]): Promise<Tool[]> {
|
|
349
363
|
const includeYield = session.requireYieldTool === true;
|
|
350
364
|
const enableLsp = session.enableLsp ?? true;
|
|
351
|
-
|
|
365
|
+
let requestedTools =
|
|
352
366
|
toolNames && toolNames.length > 0 ? [...new Set(toolNames.map(name => name.toLowerCase()))] : undefined;
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
367
|
+
const goalEnabled = session.settings.get("goal.enabled");
|
|
368
|
+
const goalModeActive = goalEnabled && session.getGoalModeState?.()?.enabled === true;
|
|
369
|
+
if (goalModeActive && requestedTools && !requestedTools.includes("goal")) {
|
|
370
|
+
requestedTools = [...requestedTools, "goal"];
|
|
356
371
|
}
|
|
357
372
|
const backends = resolveEvalBackends(session);
|
|
358
373
|
const allowPython = backends.python;
|
|
@@ -425,7 +440,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
425
440
|
|
|
426
441
|
const allTools: Record<string, ToolFactory> = { ...BUILTIN_TOOLS, ...HIDDEN_TOOLS };
|
|
427
442
|
const isToolAllowed = (name: string) => {
|
|
428
|
-
if (name === "
|
|
443
|
+
if (name === "goal") return goalEnabled && goalModeActive;
|
|
429
444
|
if (name === "lsp") return enableLsp && session.settings.get("lsp.enabled");
|
|
430
445
|
if (name === "bash") return true;
|
|
431
446
|
if (name === "eval") return allowEval;
|
|
@@ -475,7 +490,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
475
490
|
.filter(([name]) => isToolAllowed(name))
|
|
476
491
|
.map(([name, factory]) => [name, factory] as const),
|
|
477
492
|
...(includeYield ? ([["yield", HIDDEN_TOOLS.yield]] as const) : []),
|
|
478
|
-
...(
|
|
493
|
+
...(goalModeActive ? ([["goal", HIDDEN_TOOLS.goal]] as const) : []),
|
|
479
494
|
];
|
|
480
495
|
|
|
481
496
|
const baseResults = await Promise.all(
|
|
@@ -485,8 +500,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
485
500
|
}),
|
|
486
501
|
);
|
|
487
502
|
const tools = baseResults.filter((r): r is Tool => r !== null);
|
|
488
|
-
|
|
489
|
-
if (hasDeferrableTools && !tools.some(tool => tool.name === "resolve")) {
|
|
503
|
+
if (!tools.some(tool => tool.name === "resolve")) {
|
|
490
504
|
const resolveTool = await logger.time("createTools:resolve", HIDDEN_TOOLS.resolve, session);
|
|
491
505
|
if (resolveTool) {
|
|
492
506
|
tools.push(wrapToolWithMetaNotice(resolveTool));
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import { type Api,
|
|
2
|
+
import { type Api, completeSimple, type Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { type Static, Type } from "@sinclair/typebox";
|
|
5
|
+
import { extractTextContent } from "../commit/utils";
|
|
5
6
|
import { expandRoleAlias, resolveModelFromString } from "../config/model-resolver";
|
|
6
7
|
import inspectImageDescription from "../prompts/tools/inspect-image.md" with { type: "text" };
|
|
7
8
|
import inspectImageSystemPromptTemplate from "../prompts/tools/inspect-image-system.md" with { type: "text" };
|
|
@@ -30,14 +31,6 @@ export interface InspectImageToolDetails {
|
|
|
30
31
|
mimeType: string;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
function extractResponseText(message: AssistantMessage): string {
|
|
34
|
-
return message.content
|
|
35
|
-
.filter(content => content.type === "text")
|
|
36
|
-
.map(content => content.text)
|
|
37
|
-
.join("")
|
|
38
|
-
.trim();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
34
|
export class InspectImageTool implements AgentTool<typeof inspectImageSchema, InspectImageToolDetails> {
|
|
42
35
|
readonly name = "inspect_image";
|
|
43
36
|
readonly label = "InspectImage";
|
|
@@ -151,7 +144,7 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
|
|
|
151
144
|
throw new ToolError("inspect_image request aborted.");
|
|
152
145
|
}
|
|
153
146
|
|
|
154
|
-
const text =
|
|
147
|
+
const text = extractTextContent(response);
|
|
155
148
|
if (!text) {
|
|
156
149
|
throw new ToolError("inspect_image model returned no text output.");
|
|
157
150
|
}
|