@oh-my-pi/pi-coding-agent 17.0.2 → 17.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +70 -20
- package/dist/cli.js +4087 -4108
- package/dist/types/config/settings-schema.d.ts +7 -3
- package/dist/types/eval/agent-bridge.d.ts +7 -19
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +15 -1
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/lsp/types.d.ts +3 -0
- package/dist/types/mcp/transports/stdio.d.ts +29 -0
- package/dist/types/modes/components/agent-hub.d.ts +15 -0
- package/dist/types/registry/persisted-agents.d.ts +3 -0
- package/dist/types/sdk.d.ts +14 -3
- package/dist/types/session/session-entries.d.ts +6 -1
- package/dist/types/session/session-manager.d.ts +5 -0
- package/dist/types/task/executor.d.ts +26 -1
- package/dist/types/task/index.d.ts +1 -1
- package/dist/types/task/parallel.d.ts +14 -0
- package/dist/types/task/structured-subagent.d.ts +111 -0
- package/dist/types/task/types.d.ts +51 -0
- package/dist/types/tools/fetch.d.ts +4 -5
- package/dist/types/tools/hub/messaging.d.ts +5 -1
- package/dist/types/tools/index.d.ts +16 -1
- package/dist/types/tools/todo.d.ts +31 -0
- package/dist/types/tui/tree-list.d.ts +7 -0
- package/dist/types/utils/markit.d.ts +11 -0
- package/package.json +12 -12
- package/src/cli/file-processor.ts +1 -2
- package/src/config/settings-schema.ts +4 -3
- package/src/eval/__tests__/agent-bridge.test.ts +133 -47
- package/src/eval/__tests__/prelude-agent.test.ts +29 -0
- package/src/eval/agent-bridge.ts +104 -477
- package/src/eval/jl/prelude.jl +7 -6
- package/src/eval/js/shared/prelude.txt +5 -4
- package/src/eval/py/prelude.py +11 -39
- package/src/eval/rb/prelude.rb +5 -6
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +53 -7
- package/src/lsp/client.ts +57 -0
- package/src/lsp/index.ts +62 -6
- package/src/lsp/types.ts +3 -0
- package/src/mcp/oauth-flow.ts +20 -0
- package/src/mcp/transports/stdio.test.ts +269 -1
- package/src/mcp/transports/stdio.ts +152 -1
- package/src/modes/components/agent-hub.ts +1 -72
- package/src/modes/components/bash-execution.ts +7 -3
- package/src/modes/components/eval-execution.ts +3 -1
- package/src/modes/interactive-mode.ts +30 -8
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/tools/eval.md +5 -2
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/task.md +12 -8
- package/src/prompts/tools/write.md +1 -1
- package/src/registry/persisted-agents.ts +74 -0
- package/src/sdk.ts +129 -87
- package/src/session/agent-session.ts +75 -21
- package/src/session/session-entries.ts +6 -1
- package/src/session/session-manager.ts +9 -0
- package/src/session/streaming-output.ts +41 -1
- package/src/system-prompt.ts +7 -2
- package/src/task/executor.ts +99 -21
- package/src/task/index.ts +258 -429
- package/src/task/parallel.ts +43 -0
- package/src/task/persisted-revive.ts +19 -7
- package/src/task/structured-subagent.ts +642 -0
- package/src/task/types.ts +58 -0
- package/src/tools/__tests__/eval-description.test.ts +1 -1
- package/src/tools/fetch.ts +28 -105
- package/src/tools/hub/messaging.ts +16 -3
- package/src/tools/index.ts +47 -14
- package/src/tools/path-utils.ts +1 -0
- package/src/tools/read.ts +14 -26
- package/src/tools/todo.ts +126 -13
- package/src/tui/tree-list.ts +39 -0
- package/src/utils/markit.ts +12 -0
- package/src/utils/zip.ts +16 -1
|
@@ -17,9 +17,12 @@ write(path, content) → str
|
|
|
17
17
|
env(key?=None, value?=None) → str | None | dict
|
|
18
18
|
output(*ids, format?="raw", query?=None, offset?=None, limit?=None) → str | dict | list[dict]
|
|
19
19
|
tool.<name>(args) → unknown
|
|
20
|
+
Invoke any session tool; `args` = its parameter object.
|
|
20
21
|
completion(prompt, model?="default"|"smol"|"slow", system?=None, schema?=None) → str | dict
|
|
21
|
-
|
|
22
|
-
{{#if
|
|
22
|
+
Oneshot, stateless (no history/tools). `model`: "smol" fast | "default" session | "slow" most capable. `schema` (JSON-Schema) → parsed object.
|
|
23
|
+
{{#if spawns}}agent(prompt, agent?="{{spawnDefaultAgent}}", model?=None, label?=None, schema?=None, schema{{#if js}}Mode{{else}}_mode{{/if}}?="permissive", isolated?=None, apply?=None, merge?=None, handle?=False) → str | dict
|
|
24
|
+
Run a subagent → final output. `agent` selects a discovered agent; omit it to use `{{spawnDefaultAgent}}`.{{#if spawnAllowedAgentsText}} Allowed agents: {{spawnAllowedAgentsText}}.{{/if}} `schema` overrides agent/session schemas; `schemaMode`/`schema_mode`: "permissive" | "strict". Effective schemas return parsed data. `isolated` requests a worktree; `apply`/`merge` control its changes. Background via `local://` files named in the prompt. `handle` → { text, output, handle: "agent://<id>", id, agent }, parsed `data` when structured.
|
|
25
|
+
{{#if js}} JS: ONE trailing object — agent(prompt, { agent, model, label, schema, schemaMode, isolated, apply, merge, handle }).{{/if}}
|
|
23
26
|
{{/if}}
|
|
24
27
|
parallel(thunks) → list pipeline(items, ...stages) → list
|
|
25
28
|
log(message) → None phase(title) → None
|
|
@@ -15,7 +15,7 @@ Read files, directories, archives, SQLite, images, documents, internal resources
|
|
|
15
15
|
- {{#if IS_HL_MODE}}File + selector → `[foo.ts#1A2B]` snapshot header + numbered lines. Copy `[FILENAME#TAG]` for anchored edits; NEVER fabricate the tag.{{/if}}
|
|
16
16
|
- Directory → depth-limited dirent listing.
|
|
17
17
|
- SQLite (`.sqlite`, `.sqlite3`, `.db`, `.db3`): `file.db` (tables), `file.db:table` (schema+rows), `file.db:table:key` (by PK), `?limit=`/`?where=`/`?q=SELECT`.
|
|
18
|
-
- Archives (`.tar`, `.tar.gz`, `.tgz`, `.zip`): `archive.ext:path/inside/archive` reads a member.
|
|
18
|
+
- Archives (`.tar`, `.tar.gz`, `.tgz`, `.zip`, plus ZIP-based `.jar`/`.war`/`.ear`/`.apk`): `archive.ext:path/inside/archive` reads a member.
|
|
19
19
|
- Documents → extracted text. Notebooks → editable cells. Images → {{#if INSPECT_IMAGE_ENABLED}}metadata; call `inspect_image`{{else}}decoded inline{{/if}}. `:raw` bypasses converters.
|
|
20
20
|
- URLs → reader-mode clean text/markdown; `:raw` → untouched HTML. Bare `host:port` needs trailing slash.
|
|
21
21
|
- Internal URIs — all schemes take selectors. `artifact://<id>` recovers spilled output; page with `:N-M`/`:raw:N-M`.
|
|
@@ -10,18 +10,22 @@ Agents marked BLOCKING run inline — results return in this call; non-blocking
|
|
|
10
10
|
|
|
11
11
|
# Inputs
|
|
12
12
|
{{#if batchEnabled}}
|
|
13
|
-
- `context`: Shared project state
|
|
14
|
-
- `tasks[]`:
|
|
15
|
-
- `name`: CamelCase ≤32 chars (
|
|
16
|
-
- `agent`:
|
|
17
|
-
- `task`: Complete, self-contained instructions
|
|
13
|
+
- `context`: Shared project state, constraints, and contracts. Applies to the entire batch; do not duplicate this background into individual tasks.
|
|
14
|
+
- `tasks[]`: Array of subagents to spawn.
|
|
15
|
+
- `name`: A stable CamelCase identifier (≤32 chars), used to address the agent (IRC, job ids). Generated automatically if omitted.
|
|
16
|
+
- `agent`: The agent type running this item (e.g. `scout`, `reviewer`). Omitting it gives you the general-purpose worker (`{{defaultAgent}}`) — NEVER pass that name explicitly. Only omit it after checking the agent list below and finding no specialist that fits.{{#if allowedAgentsText}} Current spawn policy allows: {{allowedAgentsText}}.{{/if}}
|
|
17
|
+
- `task`: Complete, self-contained instructions. One-liners or missing acceptance criteria are PROHIBITED.
|
|
18
|
+
- `outputSchema`: Invocation-specific JSON Schema. Overrides the selected agent and parent-session schemas.
|
|
19
|
+
- `schemaMode`: `"permissive"` (default) accepts a retry-exhausted invalid result with a warning; `"strict"` fails it.
|
|
18
20
|
{{#if isolationEnabled}}
|
|
19
21
|
- `isolated`: Run in dedicated worktree, return patches. Destroyed on completion, cannot be addressed afterward.
|
|
20
22
|
{{/if}}
|
|
21
23
|
{{else}}
|
|
22
|
-
- `name`: CamelCase ≤32 chars (
|
|
23
|
-
- `agent`:
|
|
24
|
-
- `task`: Complete, self-contained instructions
|
|
24
|
+
- `name`: A stable CamelCase identifier (≤32 chars), used to address the agent (IRC, job ids). Generated automatically if omitted.
|
|
25
|
+
- `agent`: The agent type to spawn (e.g. `scout`, `reviewer`). Omitting it gives you the general-purpose worker (`{{defaultAgent}}`) — NEVER pass that name explicitly. Only omit it after checking the agent list below and finding no specialist that fits.{{#if allowedAgentsText}} Current spawn policy allows: {{allowedAgentsText}}.{{/if}}
|
|
26
|
+
- `task`: Complete, self-contained instructions. One-liners or missing acceptance criteria are PROHIBITED.
|
|
27
|
+
- `outputSchema`: Invocation-specific JSON Schema. Overrides the selected agent and parent-session schemas.
|
|
28
|
+
- `schemaMode`: `"permissive"` (default) accepts a retry-exhausted invalid result with a warning; `"strict"` fails it.
|
|
25
29
|
{{#if isolationEnabled}}
|
|
26
30
|
- `isolated`: Run in dedicated worktree, return patches.
|
|
27
31
|
{{/if}}
|
|
@@ -3,7 +3,7 @@ Creates or overwrites file at specified path.
|
|
|
3
3
|
<conditions>
|
|
4
4
|
- Creating new files explicitly required by task
|
|
5
5
|
- Replacing entire file contents when editing would be more complex
|
|
6
|
-
- Supports `.tar`, `.tar.gz`, `.tgz`, and `.
|
|
6
|
+
- Supports `.tar`, `.tar.gz`, `.tgz`, `.zip`, and ZIP-based `.jar`/`.war`/`.ear`/`.apk` archive entries via `archive.ext:path/inside/archive`
|
|
7
7
|
- Supports SQLite row operations via `db.sqlite:table` (insert), `db.sqlite:table:key` (update with JSON content, delete with empty content)
|
|
8
8
|
</conditions>
|
|
9
9
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { ADVISOR_TRANSCRIPT_FILENAME, isAdvisorTranscriptName } from "../advisor/transcript-recorder";
|
|
4
|
+
import { type AgentRegistry, MAIN_AGENT_ID } from "./agent-registry";
|
|
5
|
+
|
|
6
|
+
/** Register persisted subagent and advisor transcripts as parked registry refs. */
|
|
7
|
+
export async function registerPersistedSubagents(
|
|
8
|
+
registry: AgentRegistry,
|
|
9
|
+
sessionFile: string | null | undefined,
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
if (!sessionFile?.endsWith(".jsonl")) return;
|
|
12
|
+
const root = sessionFile.slice(0, -6);
|
|
13
|
+
await registerPersistedSubagentsFromDir(registry, root, undefined);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function registerPersistedSubagentsFromDir(
|
|
17
|
+
registry: AgentRegistry,
|
|
18
|
+
dir: string,
|
|
19
|
+
parentId: string | undefined,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
let entries: fs.Dirent[];
|
|
22
|
+
try {
|
|
23
|
+
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
24
|
+
} catch {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
if (!entry.isFile() || !entry.name.endsWith(".jsonl") || entry.name.includes(".bak")) continue;
|
|
29
|
+
const sessionFile = path.join(dir, entry.name);
|
|
30
|
+
// The advisor transcript is observability-only: register it as a non-peer
|
|
31
|
+
// `advisor` kind under its owning session so the Hub can show its read-only
|
|
32
|
+
// transcript, but it never joins agent-facing rosters and is not revivable.
|
|
33
|
+
if (isAdvisorTranscriptName(entry.name)) {
|
|
34
|
+
const owner = parentId ?? MAIN_AGENT_ID;
|
|
35
|
+
// `__advisor.jsonl` → the default advisor (no slug); `__advisor.<slug>.jsonl`
|
|
36
|
+
// → a named advisor, keyed and labeled by its slug.
|
|
37
|
+
const slug =
|
|
38
|
+
entry.name === ADVISOR_TRANSCRIPT_FILENAME ? "" : entry.name.slice("__advisor.".length, -".jsonl".length);
|
|
39
|
+
const advisorId = slug ? `${owner}/advisor:${slug}` : `${owner}/advisor`;
|
|
40
|
+
const displayName = slug ? `advisor:${slug}` : "advisor";
|
|
41
|
+
const existing = registry.get(advisorId);
|
|
42
|
+
// Never clobber a non-advisor ref that happens to share this id (a freak
|
|
43
|
+
// user task literally named `<owner>/advisor`): leave it, skip the advisor.
|
|
44
|
+
if (existing && existing.kind !== "advisor") continue;
|
|
45
|
+
if (existing?.sessionFile !== sessionFile) {
|
|
46
|
+
// The id is reused across `/new`; refresh it to the current session's file.
|
|
47
|
+
if (existing) registry.unregister(advisorId);
|
|
48
|
+
registry.register({
|
|
49
|
+
id: advisorId,
|
|
50
|
+
displayName,
|
|
51
|
+
kind: "advisor",
|
|
52
|
+
parentId: owner,
|
|
53
|
+
session: null,
|
|
54
|
+
sessionFile,
|
|
55
|
+
status: "parked",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const id = entry.name.slice(0, -6);
|
|
61
|
+
if (!registry.get(id)) {
|
|
62
|
+
registry.register({
|
|
63
|
+
id,
|
|
64
|
+
displayName: id,
|
|
65
|
+
kind: "sub",
|
|
66
|
+
parentId: parentId ?? MAIN_AGENT_ID,
|
|
67
|
+
session: null,
|
|
68
|
+
sessionFile,
|
|
69
|
+
status: "parked",
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
await registerPersistedSubagentsFromDir(registry, path.join(dir, id), id);
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/sdk.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from "./advisor";
|
|
35
35
|
import { type AsyncJob, AsyncJobManager } from "./async";
|
|
36
36
|
import { AutoLearnController, buildAutoLearnInstructions } from "./autolearn/controller";
|
|
37
|
+
import { createAutoresearchExtension } from "./autoresearch";
|
|
37
38
|
import { loadCapability } from "./capability";
|
|
38
39
|
import { type Rule, ruleCapability, setActiveRules } from "./capability/rule";
|
|
39
40
|
import { bucketRules } from "./capability/rule-buckets";
|
|
@@ -145,6 +146,7 @@ import {
|
|
|
145
146
|
} from "./system-prompt";
|
|
146
147
|
import { AgentOutputManager } from "./task/output-manager";
|
|
147
148
|
import { wrapStreamFnWithProviderConcurrency } from "./task/provider-concurrency";
|
|
149
|
+
import type { StructuredSubagentSchemaMode } from "./task/types";
|
|
148
150
|
import {
|
|
149
151
|
AUTO_THINKING,
|
|
150
152
|
type ConfiguredThinkingLevel,
|
|
@@ -482,20 +484,30 @@ export interface CreateAgentSessionOptions {
|
|
|
482
484
|
/** File-based slash commands. Default: discovered from commands/ directories */
|
|
483
485
|
slashCommands?: FileSlashCommand[];
|
|
484
486
|
|
|
485
|
-
/**
|
|
487
|
+
/**
|
|
488
|
+
* Enable MCP capabilities. `false` skips MCP discovery and ignores
|
|
489
|
+
* `mcpManager`, preventing process-global or inherited MCP access. Default:
|
|
490
|
+
* true.
|
|
491
|
+
*/
|
|
486
492
|
enableMCP?: boolean;
|
|
487
|
-
/** Existing MCP manager to reuse (skips discovery, propagates to toolSession). */
|
|
493
|
+
/** Existing MCP manager to reuse when MCP is enabled (skips discovery, propagates to toolSession). */
|
|
488
494
|
mcpManager?: MCPManager;
|
|
489
495
|
|
|
490
496
|
/** Enable LSP integration (tool, formatting, diagnostics, warmup). Default: true */
|
|
491
497
|
enableLsp?: boolean;
|
|
498
|
+
/** Whether this invocation may expose IRC. `false` removes it even for subagents. */
|
|
499
|
+
enableIrc?: boolean;
|
|
492
500
|
/** Skip subprocess-kernel availability checks and prelude warmup */
|
|
493
501
|
skipPythonPreflight?: boolean;
|
|
494
502
|
/** Tool names explicitly requested (enables disabled-by-default tools) */
|
|
495
503
|
toolNames?: string[];
|
|
504
|
+
/** Limit the session to explicitly supplied tool names, without discovered extras. */
|
|
505
|
+
restrictToolNames?: boolean;
|
|
496
506
|
|
|
497
|
-
/** Output schema for structured completion (subagents) */
|
|
507
|
+
/** Output schema for structured completion (subagents). */
|
|
498
508
|
outputSchema?: unknown;
|
|
509
|
+
/** Enforcement policy for {@link outputSchema}; defaults to legacy permissive behavior. */
|
|
510
|
+
outputSchemaMode?: StructuredSubagentSchemaMode;
|
|
499
511
|
/** Whether to include the yield tool by default */
|
|
500
512
|
requireYieldTool?: boolean;
|
|
501
513
|
/** Task recursion depth (for subagent sessions). Default: 0 */
|
|
@@ -1544,7 +1556,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1544
1556
|
let session!: AgentSession;
|
|
1545
1557
|
let hasSession = false;
|
|
1546
1558
|
let hasRegistered = false;
|
|
1547
|
-
const
|
|
1559
|
+
const restrictToolNames = options.restrictToolNames === true;
|
|
1560
|
+
const enableLsp = !restrictToolNames && (options.enableLsp ?? true);
|
|
1548
1561
|
const asyncMaxJobs = Math.min(100, Math.max(1, settings.get("async.maxJobs") ?? 100));
|
|
1549
1562
|
const ASYNC_INLINE_RESULT_MAX_CHARS = 12_000;
|
|
1550
1563
|
const ASYNC_PREVIEW_MAX_CHARS = 4_000;
|
|
@@ -1641,9 +1654,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1641
1654
|
setActiveToolNames,
|
|
1642
1655
|
hasUI: options.hasUI ?? false,
|
|
1643
1656
|
enableLsp,
|
|
1657
|
+
enableIrc: restrictToolNames ? false : options.enableIrc,
|
|
1658
|
+
restrictToolNames,
|
|
1644
1659
|
get hasEditTool() {
|
|
1645
1660
|
const requestedToolNames = options.toolNames ? normalizeToolNames(options.toolNames) : undefined;
|
|
1646
|
-
return
|
|
1661
|
+
return restrictToolNames
|
|
1662
|
+
? requestedToolNames?.includes("edit") === true
|
|
1663
|
+
: !requestedToolNames || requestedToolNames.includes("edit");
|
|
1647
1664
|
},
|
|
1648
1665
|
skipPythonPreflight: options.skipPythonPreflight,
|
|
1649
1666
|
contextFiles,
|
|
@@ -1655,6 +1672,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1655
1672
|
rules: allRules,
|
|
1656
1673
|
eventBus,
|
|
1657
1674
|
outputSchema: options.outputSchema,
|
|
1675
|
+
outputSchemaMode: options.outputSchemaMode,
|
|
1658
1676
|
requireYieldTool: options.requireYieldTool,
|
|
1659
1677
|
prewalkArmed: options.prewalk !== undefined,
|
|
1660
1678
|
taskDepth: options.taskDepth ?? 0,
|
|
@@ -1771,10 +1789,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1771
1789
|
// Create built-in tools (already wrapped with meta notice formatting)
|
|
1772
1790
|
const builtinTools = await logger.time("createAllTools", createTools, toolSession, options.toolNames);
|
|
1773
1791
|
|
|
1774
|
-
//
|
|
1775
|
-
|
|
1792
|
+
// Restricted sessions cannot inherit or discover MCP capabilities.
|
|
1793
|
+
const enableMCP = !restrictToolNames && (options.enableMCP ?? true);
|
|
1794
|
+
let mcpManager: MCPManager | undefined = enableMCP ? options.mcpManager : undefined;
|
|
1776
1795
|
toolSession.mcpManager = mcpManager;
|
|
1777
|
-
|
|
1796
|
+
toolSession.enableMCP = enableMCP;
|
|
1778
1797
|
const deferMCPDiscoveryForUI = enableMCP && !mcpManager && options.hasUI === true;
|
|
1779
1798
|
const customTools: CustomTool[] = [];
|
|
1780
1799
|
let startDeferredMCPDiscovery: ((liveSession: AgentSession) => void) | undefined;
|
|
@@ -1860,58 +1879,62 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1860
1879
|
// to mirror the AsyncJobManager ownership rule.
|
|
1861
1880
|
if (mcpManager && !options.parentTaskPrefix) MCPManager.setInstance(mcpManager);
|
|
1862
1881
|
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1882
|
+
const builtInToolNames = builtinTools.map(t => t.name);
|
|
1883
|
+
let customToolPaths: ToolPathWithSource[] = [];
|
|
1884
|
+
const inlineExtensions: ExtensionFactory[] = [];
|
|
1885
|
+
if (!restrictToolNames) {
|
|
1886
|
+
// Add image tools when generation is enabled and either no explicit tool
|
|
1887
|
+
// whitelist was given or it names `generate_image`. Unlike built-in tools
|
|
1888
|
+
// (filtered in `createTools`), custom tools are force-activated via
|
|
1889
|
+
// `alwaysInclude` below, so an explicit `--no-tools`/whitelist must be
|
|
1890
|
+
// honored here or image-gen would leak past every filter (issue #5305).
|
|
1891
|
+
const imageGenRequested = !options.toolNames || options.toolNames.includes("generate_image");
|
|
1892
|
+
if (settings.get("generate_image.enabled") && imageGenRequested) {
|
|
1893
|
+
const imageGenTools = await logger.time("getImageGenTools", () => getImageGenTools(modelRegistry, model));
|
|
1894
|
+
if (imageGenTools.length > 0) {
|
|
1895
|
+
customTools.push(...(imageGenTools as unknown as CustomTool[]));
|
|
1896
|
+
}
|
|
1873
1897
|
}
|
|
1874
|
-
}
|
|
1875
1898
|
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1899
|
+
if (settings.get("speechgen.enabled")) {
|
|
1900
|
+
customTools.push(ttsTool as unknown as CustomTool);
|
|
1901
|
+
}
|
|
1879
1902
|
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1903
|
+
// Add web search tools
|
|
1904
|
+
if (options.toolNames?.includes("web_search")) {
|
|
1905
|
+
customTools.push(...getSearchTools());
|
|
1906
|
+
}
|
|
1884
1907
|
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1908
|
+
// Discover custom tools from `.omp/tools/`, `.claude/tools/`, plugins, etc.
|
|
1909
|
+
// Subagents reuse the parent's scan via `preloadedCustomToolPaths` to skip
|
|
1910
|
+
// the FS walk, but ALWAYS re-call `loadCustomTools` here so factories bind
|
|
1911
|
+
// to THIS session's `CustomToolAPI` (cwd, exec, pushPendingAction, UI).
|
|
1912
|
+
// Forwarding the parent's `LoadedCustomTool[]` directly would route tool
|
|
1913
|
+
// execution back through the parent — wrong for isolated tasks and for
|
|
1914
|
+
// pending-action queueing.
|
|
1915
|
+
customToolPaths =
|
|
1916
|
+
options.preloadedCustomToolPaths ??
|
|
1917
|
+
(await logger.time("discoverCustomToolPaths", () => discoverCustomToolPaths([], cwd)));
|
|
1918
|
+
const customToolsLoadResult = await logger.time("loadCustomTools", () =>
|
|
1919
|
+
loadCustomTools(customToolPaths, cwd, builtInToolNames, action => queueResolveHandler(toolSession, action)),
|
|
1920
|
+
);
|
|
1921
|
+
for (const { path, error } of customToolsLoadResult.errors) {
|
|
1922
|
+
logger.error("Custom tool load failed", { path, error });
|
|
1923
|
+
}
|
|
1924
|
+
if (customToolsLoadResult.tools.length > 0) {
|
|
1925
|
+
customTools.push(...customToolsLoadResult.tools.map(loaded => loaded.tool));
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
inlineExtensions.push(...(options.extensions ?? []));
|
|
1929
|
+
inlineExtensions.push(createAutoresearchExtension);
|
|
1930
|
+
if (customTools.length > 0) {
|
|
1931
|
+
inlineExtensions.push(createCustomToolsExtension(customTools));
|
|
1932
|
+
}
|
|
1904
1933
|
}
|
|
1905
1934
|
// Forward the path list (NOT the loaded tools) to subagents so they
|
|
1906
1935
|
// re-bind under their own `CustomToolAPI` while skipping the FS scan.
|
|
1907
1936
|
toolSession.customToolPaths = customToolPaths;
|
|
1908
1937
|
|
|
1909
|
-
const inlineExtensions: ExtensionFactory[] = options.extensions ? [...options.extensions] : [];
|
|
1910
|
-
inlineExtensions.push((await import("./autoresearch")).createAutoresearchExtension);
|
|
1911
|
-
if (customTools.length > 0) {
|
|
1912
|
-
inlineExtensions.push(createCustomToolsExtension(customTools));
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
1938
|
// Load extensions. Three paths:
|
|
1916
1939
|
// 1. `preloadedExtensions` (CLI): caller already loaded — reuse the
|
|
1917
1940
|
// Extension instances. Shallow-clone `extensions` so the inline
|
|
@@ -1926,7 +1949,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1926
1949
|
// the flag and pre-resolved the result already reflects that choice.
|
|
1927
1950
|
let extensionPaths: string[];
|
|
1928
1951
|
let extensionsResult: LoadExtensionsResult;
|
|
1929
|
-
if (
|
|
1952
|
+
if (restrictToolNames) {
|
|
1953
|
+
// Allocate a session runtime without evaluating caller-provided extension
|
|
1954
|
+
// instances, paths, or factories.
|
|
1955
|
+
extensionPaths = [];
|
|
1956
|
+
extensionsResult = await loadExtensions([], cwd, eventBus);
|
|
1957
|
+
} else if (options.preloadedExtensions) {
|
|
1930
1958
|
extensionsResult = {
|
|
1931
1959
|
...options.preloadedExtensions,
|
|
1932
1960
|
extensions: [...options.preloadedExtensions.extensions],
|
|
@@ -2235,11 +2263,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2235
2263
|
}
|
|
2236
2264
|
}
|
|
2237
2265
|
|
|
2238
|
-
//
|
|
2239
|
-
const customCommandsResult: CustomCommandsLoadResult =
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2266
|
+
// Restricted sessions do not discover or evaluate custom command modules.
|
|
2267
|
+
const customCommandsResult: CustomCommandsLoadResult =
|
|
2268
|
+
options.disableExtensionDiscovery || restrictToolNames
|
|
2269
|
+
? { commands: [], errors: [] }
|
|
2270
|
+
: await logger.time("discoverCustomCommands", loadCustomCommandsInternal, { cwd, agentDir });
|
|
2271
|
+
if (!options.disableExtensionDiscovery && !restrictToolNames) {
|
|
2243
2272
|
for (const { path, error } of customCommandsResult.errors) {
|
|
2244
2273
|
logger.error("Failed to load custom command", { path, error });
|
|
2245
2274
|
}
|
|
@@ -2284,8 +2313,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2284
2313
|
});
|
|
2285
2314
|
const toolContextStore = new ToolContextStore(getSessionContext);
|
|
2286
2315
|
|
|
2287
|
-
const registeredTools = extensionRunner.getAllRegisteredTools();
|
|
2288
|
-
const sdkCustomTools =
|
|
2316
|
+
const registeredTools = restrictToolNames ? [] : extensionRunner.getAllRegisteredTools();
|
|
2317
|
+
const sdkCustomTools = restrictToolNames
|
|
2318
|
+
? []
|
|
2319
|
+
: (options.customTools?.filter(tool => !isLegacyBuiltinToolDefinition(tool)) ?? []);
|
|
2289
2320
|
const allCustomTools = [
|
|
2290
2321
|
...registeredTools,
|
|
2291
2322
|
...sdkCustomTools.map(tool => {
|
|
@@ -2308,7 +2339,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2308
2339
|
toolRegistry.set(tool.name, tool);
|
|
2309
2340
|
builtInRegistryToolNames.add(tool.name);
|
|
2310
2341
|
}
|
|
2311
|
-
if (!toolRegistry.has("goal") && settings.get("goal.enabled")) {
|
|
2342
|
+
if (!restrictToolNames && !toolRegistry.has("goal") && settings.get("goal.enabled")) {
|
|
2312
2343
|
const goalTool = await logger.time("createTools:goal:session", HIDDEN_TOOLS.goal, toolSession);
|
|
2313
2344
|
if (goalTool) {
|
|
2314
2345
|
toolRegistry.set(goalTool.name, wrapToolWithMetaNotice(goalTool));
|
|
@@ -2361,7 +2392,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2361
2392
|
const hasDeferrableTools = Array.from(toolRegistry.values()).some(tool => tool.deferrable === true);
|
|
2362
2393
|
const hasXdevTools = (toolSession.xdevRegistry?.size ?? 0) > 0;
|
|
2363
2394
|
const planModeAvailable = settings.get("plan.enabled");
|
|
2364
|
-
if (hasDeferrableTools || hasXdevTools || planModeAvailable || deferMCPDiscoveryForUI) {
|
|
2395
|
+
if (!restrictToolNames && (hasDeferrableTools || hasXdevTools || planModeAvailable || deferMCPDiscoveryForUI)) {
|
|
2365
2396
|
await ensureWriteRegistered();
|
|
2366
2397
|
}
|
|
2367
2398
|
|
|
@@ -2401,8 +2432,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2401
2432
|
): Promise<BuildSystemPromptResult> => {
|
|
2402
2433
|
toolContextStore.setToolNames(toolNames);
|
|
2403
2434
|
const promptTools = buildSystemPromptToolMetadata(tools);
|
|
2404
|
-
const memoryBackend = await resolveMemoryBackend(settings);
|
|
2405
|
-
const memoryInstructions =
|
|
2435
|
+
const memoryBackend = restrictToolNames ? undefined : await resolveMemoryBackend(settings);
|
|
2436
|
+
const memoryInstructions = memoryBackend
|
|
2437
|
+
? await memoryBackend.buildDeveloperInstructions(agentDir, settings, session)
|
|
2438
|
+
: undefined;
|
|
2406
2439
|
|
|
2407
2440
|
// Build combined append prompt: memory instructions + auto-learn guidance
|
|
2408
2441
|
// + MCP server instructions. For UI sessions MCP discovery is deferred, so
|
|
@@ -2417,10 +2450,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2417
2450
|
// session-start build — so a subagent that filtered them out, a mid-session
|
|
2418
2451
|
// enable that never built them, or a same-named custom tool while auto-learn
|
|
2419
2452
|
// is off all get no guidance.
|
|
2420
|
-
const autoLearnInstructions =
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2453
|
+
const autoLearnInstructions = restrictToolNames
|
|
2454
|
+
? undefined
|
|
2455
|
+
: buildAutoLearnInstructions({
|
|
2456
|
+
manageSkill: builtInToolNames.includes("manage_skill"),
|
|
2457
|
+
learn: builtInToolNames.includes("learn"),
|
|
2458
|
+
});
|
|
2424
2459
|
const appendParts: string[] = [];
|
|
2425
2460
|
if (memoryInstructions) appendParts.push(memoryInstructions);
|
|
2426
2461
|
if (autoLearnInstructions) appendParts.push(autoLearnInstructions);
|
|
@@ -2452,7 +2487,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2452
2487
|
cwd,
|
|
2453
2488
|
xdevTools: toolSession.xdevRegistry?.entries() ?? [],
|
|
2454
2489
|
xdevDocs: toolSession.xdevRegistry?.docsAll() ?? "",
|
|
2455
|
-
autoQaEnabled: isAutoQaEnabled(settings),
|
|
2490
|
+
autoQaEnabled: !restrictToolNames && isAutoQaEnabled(settings),
|
|
2456
2491
|
resolvedCustomPrompt: options.customSystemPrompt,
|
|
2457
2492
|
skills: session?.skills ?? skills,
|
|
2458
2493
|
contextFiles,
|
|
@@ -2469,11 +2504,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2469
2504
|
eagerTasksAlways,
|
|
2470
2505
|
taskBatch: settings.get("task.batch"),
|
|
2471
2506
|
taskMaxConcurrency: settings.get("task.maxConcurrency"),
|
|
2472
|
-
taskIrcEnabled: isIrcEnabled(settings, options.taskDepth ?? 0),
|
|
2507
|
+
taskIrcEnabled: !restrictToolNames && isIrcEnabled(settings, options.taskDepth ?? 0),
|
|
2473
2508
|
secretsEnabled,
|
|
2474
2509
|
workspaceTree: workspaceTreePromise,
|
|
2475
2510
|
includeWorkspaceTree,
|
|
2476
|
-
memoryRootEnabled: memoryBackend
|
|
2511
|
+
memoryRootEnabled: memoryBackend?.id === "local",
|
|
2477
2512
|
model: getActiveModelString(),
|
|
2478
2513
|
includeModelInPrompt: settings.get("includeModelInPrompt"),
|
|
2479
2514
|
personality: agentKind === "sub" ? "none" : settings.get("personality"),
|
|
@@ -2514,7 +2549,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2514
2549
|
// exactly the builtins createTools built (`builtInToolNames` — provenance, so a
|
|
2515
2550
|
// same-named custom/extension tool is never force-activated when auto-learn is
|
|
2516
2551
|
// off) to keep guidance, controller, and the active set consistent.
|
|
2517
|
-
if (explicitlyRequestedToolNames) {
|
|
2552
|
+
if (!restrictToolNames && explicitlyRequestedToolNames) {
|
|
2518
2553
|
for (const name of ["manage_skill", "learn"]) {
|
|
2519
2554
|
if (builtInToolNames.includes(name) && !explicitlyRequestedToolNames.includes(name)) {
|
|
2520
2555
|
explicitlyRequestedToolNames.push(name);
|
|
@@ -2538,11 +2573,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2538
2573
|
: requestedActiveToolNames.filter(name => !defaultInactiveToolNames.has(name));
|
|
2539
2574
|
let initialToolNames = [...initialRequestedActiveToolNames];
|
|
2540
2575
|
|
|
2541
|
-
// Custom tools and extension-registered tools are always included regardless of toolNames filter
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2576
|
+
// Custom tools and extension-registered tools are always included regardless of toolNames filter.
|
|
2577
|
+
// Restricted callers own the list, so never widen it with registered tools.
|
|
2578
|
+
const alwaysInclude: string[] = restrictToolNames
|
|
2579
|
+
? []
|
|
2580
|
+
: [
|
|
2581
|
+
...sdkCustomTools.map(t => (isCustomTool(t) ? t.name : t.name)),
|
|
2582
|
+
...registeredTools.filter(t => !t.definition.defaultInactive).map(t => t.definition.name),
|
|
2583
|
+
];
|
|
2546
2584
|
for (const name of alwaysInclude) {
|
|
2547
2585
|
if (toolRegistry.has(name) && !initialToolNames.includes(name)) {
|
|
2548
2586
|
initialToolNames.push(name);
|
|
@@ -2712,6 +2750,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2712
2750
|
}
|
|
2713
2751
|
return result;
|
|
2714
2752
|
};
|
|
2753
|
+
const kimiApiFormatSetting = settings.get("providers.kimiApiFormat");
|
|
2754
|
+
const kimiApiFormat = kimiApiFormatSetting === "auto" ? undefined : kimiApiFormatSetting;
|
|
2715
2755
|
agent = new Agent({
|
|
2716
2756
|
initialState: {
|
|
2717
2757
|
systemPrompt,
|
|
@@ -2745,7 +2785,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2745
2785
|
presencePenalty: settings.get("presencePenalty") >= 0 ? settings.get("presencePenalty") : undefined,
|
|
2746
2786
|
repetitionPenalty: settings.get("repetitionPenalty") >= 0 ? settings.get("repetitionPenalty") : undefined,
|
|
2747
2787
|
hideThinkingSummary: settings.get("omitThinking"),
|
|
2748
|
-
kimiApiFormat
|
|
2788
|
+
kimiApiFormat,
|
|
2749
2789
|
preferWebsockets: preferOpenAICodexWebsockets,
|
|
2750
2790
|
getToolContext: tc => toolContextStore.getContext(tc),
|
|
2751
2791
|
getApiKey: requestModel => modelRegistry.resolver(requestModel, agent.sessionId),
|
|
@@ -3078,7 +3118,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3078
3118
|
serviceTierResolver: agent.serviceTierResolver,
|
|
3079
3119
|
hideThinkingSummary: agent.hideThinkingSummary,
|
|
3080
3120
|
maxRetryDelayMs: agent.maxRetryDelayMs,
|
|
3081
|
-
kimiApiFormat
|
|
3121
|
+
kimiApiFormat,
|
|
3082
3122
|
preferWebsockets: preferOpenAICodexWebsockets,
|
|
3083
3123
|
getToolContext: toolCall => toolContextStore.getContext(toolCall),
|
|
3084
3124
|
streamFn: settingsAwareStreamFn,
|
|
@@ -3111,15 +3151,17 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3111
3151
|
// and the tools; the fire-time re-check in `#onAgentEnd` still handles a
|
|
3112
3152
|
// mid-session DISABLE. The subscription lives for the session's lifetime; the
|
|
3113
3153
|
// reference is intentionally discarded (the listener retains it).
|
|
3114
|
-
if (
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3154
|
+
if (!restrictToolNames) {
|
|
3155
|
+
if (settings.get("autolearn.enabled") && taskDepth === 0) {
|
|
3156
|
+
await logger.time("startMemoryStartupTask", startMemoryBackend);
|
|
3157
|
+
new AutoLearnController({
|
|
3158
|
+
session,
|
|
3159
|
+
settings,
|
|
3160
|
+
capture: content => session.runAutolearnCapture(signal => runAutoLearnCapture(content, signal)),
|
|
3161
|
+
});
|
|
3162
|
+
} else {
|
|
3163
|
+
void logger.time("startMemoryStartupTask", startMemoryBackend);
|
|
3164
|
+
}
|
|
3123
3165
|
}
|
|
3124
3166
|
|
|
3125
3167
|
// Wire MCP manager callbacks to session for reactive tool updates.
|