@selesai/code 0.8.7 → 0.8.8
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 +15 -1
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.js +11 -0
- package/dist/cli/args.test.d.ts +1 -0
- package/dist/cli/args.test.js +24 -0
- package/dist/core/agent-session.d.ts +4 -1
- package/dist/core/agent-session.js +5 -6
- package/dist/core/experimental.d.ts +4 -0
- package/dist/core/experimental.js +4 -0
- package/dist/core/extensions/types.d.ts +4 -0
- package/dist/core/keybindings.d.ts +24 -0
- package/dist/core/model-config.d.ts +5 -0
- package/dist/core/model-config.js +1 -0
- package/dist/core/project-trust.js +2 -2
- package/dist/core/sdk.d.ts +5 -3
- package/dist/core/sdk.js +2 -1
- package/dist/core/session-manager.js +2 -2
- package/dist/core/settings-manager.d.ts +6 -0
- package/dist/core/settings-manager.js +12 -0
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/tools/bash.js +2 -0
- package/dist/core/tools/edit.js +2 -0
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/grep.js +1 -1
- package/dist/core/tools/read.js +2 -0
- package/dist/core/tools/write.js +2 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +0 -18
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +26 -0
- package/dist/extensions/tokenin-onboarding.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/main.js +4 -0
- package/dist/modes/interactive/components/model-selector.js +2 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
- package/dist/modes/interactive/components/settings-selector.js +10 -0
- package/dist/modes/interactive/components/tool-execution.js +10 -1
- package/dist/modes/interactive/interactive-mode.d.ts +8 -2
- package/dist/modes/interactive/interactive-mode.js +81 -25
- package/dist/modes/interactive/model-catalog-refresh.d.ts +6 -0
- package/dist/modes/interactive/model-catalog-refresh.js +33 -0
- package/dist/modes/interactive/theme/dark.json +2 -0
- package/dist/modes/interactive/theme/light.json +2 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
- package/dist/modes/interactive/theme/theme-controller.js +25 -11
- package/dist/modes/interactive/theme/theme-schema.json +9 -1
- package/dist/modes/interactive/theme/theme.d.ts +5 -3
- package/dist/modes/interactive/theme/theme.js +12 -2
- package/dist/utils/abort.d.ts +4 -0
- package/dist/utils/abort.js +47 -0
- package/dist/utils/tools-manager.d.ts +5 -1
- package/dist/utils/tools-manager.js +12 -21
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@selesai/code` will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [0.8.8] - 2026-08-18
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **`--use-theme <name>` CLI flag.** Sets the initial interactive theme for a run; the fullscreen theme controller applies an invocation-level theme before settings load, so the flag wins until you change the theme in `/settings`.
|
|
9
|
+
- **Transcript search highlighting.** The fullscreen TUI now highlights search matches live in the transcript, themeable via new optional `searchMatchBg`/`searchMatchText` colors (falling back to `selectedBg`/`text`).
|
|
10
|
+
- **Fullscreen exit output option.** `/settings` gained a **Fullscreen exit output** entry: exiting fullscreen can print the transcript as before (`"transcript"`) or only a session resume hint (`"resume-hint"`) via the new `fullscreenExitOutput` setting.
|
|
11
|
+
- **`defaultTools` setting.** Seeds the initial built-in tool selection for `createAgentSession`; when set, new sessions start with only the listed tools instead of the `read`/`bash`/`edit`/`write` default.
|
|
12
|
+
- **`sendUserMessage` template expansion.** Extensions can pass `expandPromptTemplates: true` to dispatch extension commands and expand skill commands/prompt templates from a sent user message.
|
|
13
|
+
- **Experimental strict tool sampling.** Under `PI_EXPERIMENTAL=1`, the built-in read/edit/write/bash tools request `json_schema` tool output with `strict: "prefer"`.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **Managed-tool downloads moved off the startup path.** The TUI mounts and stays responsive while `fd`/`rg` install; download progress and warnings are reported as chat status lines (with an `app.tools.expand` hint) instead of raw `console.log` output, and the interactive startup accepts input (with interrupt/exit/submit feedback) while it completes.
|
|
17
|
+
- **Collapsed large tool output.** Tool components now preview only the first 10 lines with an inline `... (N more lines)` expand hint instead of dumping the whole block.
|
|
18
|
+
- **Shared model-catalog refreshes.** Concurrent interactive all-catalog refreshes reuse one in-flight refresh while keeping each caller's cancellation independent, and refreshes are bounded by a short timeout.
|
|
19
|
+
- **Runtime dependencies updated to Pi 0.84.2.** `@earendil-works/pi-ai`, `@earendil-works/pi-tui`, and `@earendil-works/pi-agent-core` moved to 0.84.2; `openAIResponses` model config gained an optional `supportsAdditionalTools` flag; `exportToHtml` accepts a `themeName` option.
|
|
6
20
|
|
|
7
21
|
## [0.8.7] - 2026-08-17
|
|
8
22
|
|
package/dist/cli/args.d.ts
CHANGED
package/dist/cli/args.js
CHANGED
|
@@ -142,6 +142,16 @@ export function parseArgs(args) {
|
|
|
142
142
|
else if (arg === "--no-prompt-templates" || arg === "-np") {
|
|
143
143
|
result.noPromptTemplates = true;
|
|
144
144
|
}
|
|
145
|
+
else if (arg === "--use-theme") {
|
|
146
|
+
const themeName = args[i + 1];
|
|
147
|
+
if (themeName === undefined || themeName.startsWith("-")) {
|
|
148
|
+
result.diagnostics.push({ type: "error", message: "--use-theme requires a theme name" });
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
result.useTheme = themeName;
|
|
152
|
+
i++;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
145
155
|
else if (arg === "--no-themes") {
|
|
146
156
|
result.noThemes = true;
|
|
147
157
|
}
|
|
@@ -272,6 +282,7 @@ ${chalk.bold("Options:")}
|
|
|
272
282
|
--prompt-template <path> Load a prompt template file or directory (can be used multiple times)
|
|
273
283
|
--no-prompt-templates, -np Disable prompt template discovery and loading
|
|
274
284
|
--theme <path> Load a theme file or directory (can be used multiple times)
|
|
285
|
+
--use-theme <name[/name]> Set the initial interactive theme for this run
|
|
275
286
|
--no-themes Disable theme discovery and loading
|
|
276
287
|
--no-context-files, -nc Disable AGENTS.md and CLAUDE.md discovery and loading
|
|
277
288
|
--export <file> Export session file to HTML and exit
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { parseArgs } from "./args.js";
|
|
3
|
+
describe("parseArgs --use-theme", () => {
|
|
4
|
+
test("parses --use-theme with a theme name", () => {
|
|
5
|
+
const result = parseArgs(["--use-theme", "nord-border", "hello"]);
|
|
6
|
+
expect(result.useTheme).toBe("nord-border");
|
|
7
|
+
expect(result.messages).toEqual(["hello"]);
|
|
8
|
+
});
|
|
9
|
+
test("accepts light/dark slash form", () => {
|
|
10
|
+
const result = parseArgs(["--use-theme", "light/dark"]);
|
|
11
|
+
expect(result.useTheme).toBe("light/dark");
|
|
12
|
+
});
|
|
13
|
+
test("reports an error when --use-theme is missing a theme name", () => {
|
|
14
|
+
const result = parseArgs(["--use-theme"]);
|
|
15
|
+
expect(result.useTheme).toBeUndefined();
|
|
16
|
+
expect(result.diagnostics.some((d) => d.type === "error")).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
test("reports an error when --use-theme precedes a flag", () => {
|
|
19
|
+
const result = parseArgs(["--use-theme", "--no-session"]);
|
|
20
|
+
expect(result.useTheme).toBeUndefined();
|
|
21
|
+
expect(result.noSession).toBe(true);
|
|
22
|
+
expect(result.diagnostics.some((d) => d.type === "error")).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -423,6 +423,7 @@ export declare class AgentSession {
|
|
|
423
423
|
*/
|
|
424
424
|
sendUserMessage(content: string | (TextContent | ImageContent)[], options?: {
|
|
425
425
|
deliverAs?: "steer" | "followUp";
|
|
426
|
+
expandPromptTemplates?: boolean;
|
|
426
427
|
}): Promise<void>;
|
|
427
428
|
/**
|
|
428
429
|
* Clear all queued messages and return them.
|
|
@@ -634,7 +635,9 @@ export declare class AgentSession {
|
|
|
634
635
|
* @param outputPath Optional output path (defaults to session directory)
|
|
635
636
|
* @returns Path to exported file
|
|
636
637
|
*/
|
|
637
|
-
exportToHtml(outputPath?: string
|
|
638
|
+
exportToHtml(outputPath?: string, options?: {
|
|
639
|
+
themeName?: string;
|
|
640
|
+
}): Promise<string>;
|
|
638
641
|
/**
|
|
639
642
|
* Export the current session branch to a JSONL file.
|
|
640
643
|
* Writes the session header followed by all entries on the current branch path.
|
|
@@ -1133,7 +1133,7 @@ export class AgentSession {
|
|
|
1133
1133
|
if (options?.deliverAs === "nextTurn") {
|
|
1134
1134
|
this._pendingNextTurnMessages.push(appMessage);
|
|
1135
1135
|
}
|
|
1136
|
-
else if (this.isStreaming) {
|
|
1136
|
+
else if (this.isStreaming && options?.triggerTurn !== false) {
|
|
1137
1137
|
if (options?.deliverAs === "followUp") {
|
|
1138
1138
|
this.agent.followUp(appMessage);
|
|
1139
1139
|
}
|
|
@@ -1180,9 +1180,9 @@ export class AgentSession {
|
|
|
1180
1180
|
if (images.length === 0)
|
|
1181
1181
|
images = undefined;
|
|
1182
1182
|
}
|
|
1183
|
-
// Use prompt() with expandPromptTemplates
|
|
1183
|
+
// Use prompt() with expandPromptTemplates to control command handling and template expansion
|
|
1184
1184
|
await this.prompt(text, {
|
|
1185
|
-
expandPromptTemplates: false,
|
|
1185
|
+
expandPromptTemplates: options?.expandPromptTemplates ?? false,
|
|
1186
1186
|
streamingBehavior: options?.deliverAs,
|
|
1187
1187
|
images,
|
|
1188
1188
|
source: "extension",
|
|
@@ -2659,9 +2659,8 @@ export class AgentSession {
|
|
|
2659
2659
|
* @param outputPath Optional output path (defaults to session directory)
|
|
2660
2660
|
* @returns Path to exported file
|
|
2661
2661
|
*/
|
|
2662
|
-
async exportToHtml(outputPath) {
|
|
2663
|
-
const
|
|
2664
|
-
const themeName = configuredThemeName && getThemeByName(configuredThemeName) ? configuredThemeName : undefined;
|
|
2662
|
+
async exportToHtml(outputPath, options = {}) {
|
|
2663
|
+
const themeName = [options.themeName, this.settingsManager.getTheme()].find((candidate) => candidate !== undefined && getThemeByName(candidate) !== undefined);
|
|
2665
2664
|
// Create tool renderer if we have an extension runner (for custom tool HTML rendering)
|
|
2666
2665
|
const toolRenderer = createToolHtmlRenderer({
|
|
2667
2666
|
getToolDefinition: (name) => this.getToolDefinition(name),
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
const PREFER_STRICT_TOOL_SAMPLING = { type: "json_schema", strict: "prefer" };
|
|
1
2
|
export function areExperimentalFeaturesEnabled() {
|
|
2
3
|
return process.env.PI_EXPERIMENTAL === "1";
|
|
3
4
|
}
|
|
5
|
+
export function getExperimentalToolSampling() {
|
|
6
|
+
return areExperimentalFeaturesEnabled() ? PREFER_STRICT_TOOL_SAMPLING : undefined;
|
|
7
|
+
}
|
|
@@ -301,6 +301,7 @@ export interface ReplacedSessionContext extends ExtensionCommandContext {
|
|
|
301
301
|
}): Promise<void>;
|
|
302
302
|
sendUserMessage(content: string | (TextContent | ImageContent)[], options?: {
|
|
303
303
|
deliverAs?: "steer" | "followUp";
|
|
304
|
+
expandPromptTemplates?: boolean;
|
|
304
305
|
}): Promise<void>;
|
|
305
306
|
}
|
|
306
307
|
/** Rendering options for tool results */
|
|
@@ -926,9 +927,11 @@ export interface ExtensionAPI {
|
|
|
926
927
|
/**
|
|
927
928
|
* Send a user message to the agent. Always triggers a turn.
|
|
928
929
|
* When the agent is streaming, use deliverAs to specify how to queue the message.
|
|
930
|
+
* Set expandPromptTemplates to dispatch extension commands and expand skill commands and prompt templates.
|
|
929
931
|
*/
|
|
930
932
|
sendUserMessage(content: string | (TextContent | ImageContent)[], options?: {
|
|
931
933
|
deliverAs?: "steer" | "followUp";
|
|
934
|
+
expandPromptTemplates?: boolean;
|
|
932
935
|
}): void;
|
|
933
936
|
/** Append a custom entry to the session for state persistence (not sent to LLM). */
|
|
934
937
|
appendEntry<T = unknown>(customType: string, data?: T): void;
|
|
@@ -1124,6 +1127,7 @@ export type SendMessageHandler = <T = unknown>(message: Pick<CustomMessage<T>, "
|
|
|
1124
1127
|
}) => void;
|
|
1125
1128
|
export type SendUserMessageHandler = (content: string | (TextContent | ImageContent)[], options?: {
|
|
1126
1129
|
deliverAs?: "steer" | "followUp";
|
|
1130
|
+
expandPromptTemplates?: boolean;
|
|
1127
1131
|
}) => void;
|
|
1128
1132
|
export type AppendEntryHandler = <T = unknown>(customType: string, data?: T) => void;
|
|
1129
1133
|
export type SetSessionNameHandler = (name: string) => void;
|
|
@@ -197,6 +197,14 @@ export declare const KEYBINDINGS: {
|
|
|
197
197
|
readonly defaultKeys: [];
|
|
198
198
|
readonly description: "Scroll viewport down half a page";
|
|
199
199
|
};
|
|
200
|
+
readonly "tui.altScreen.lineUp": {
|
|
201
|
+
readonly defaultKeys: [];
|
|
202
|
+
readonly description: "Scroll viewport up one line";
|
|
203
|
+
};
|
|
204
|
+
readonly "tui.altScreen.lineDown": {
|
|
205
|
+
readonly defaultKeys: [];
|
|
206
|
+
readonly description: "Scroll viewport down one line";
|
|
207
|
+
};
|
|
200
208
|
readonly "tui.altScreen.previousPrompt": {
|
|
201
209
|
readonly defaultKeys: "ctrl+shift+up";
|
|
202
210
|
readonly description: "Jump to previous semantic prompt";
|
|
@@ -205,6 +213,22 @@ export declare const KEYBINDINGS: {
|
|
|
205
213
|
readonly defaultKeys: "ctrl+shift+down";
|
|
206
214
|
readonly description: "Jump to next semantic prompt";
|
|
207
215
|
};
|
|
216
|
+
readonly "tui.altScreen.search": {
|
|
217
|
+
readonly defaultKeys: "ctrl+shift+f";
|
|
218
|
+
readonly description: "Search the primary scroll view";
|
|
219
|
+
};
|
|
220
|
+
readonly "tui.altScreen.searchNext": {
|
|
221
|
+
readonly defaultKeys: ["enter", "ctrl+g"];
|
|
222
|
+
readonly description: "Select the next search match";
|
|
223
|
+
};
|
|
224
|
+
readonly "tui.altScreen.searchPrevious": {
|
|
225
|
+
readonly defaultKeys: ["shift+enter", "ctrl+shift+g"];
|
|
226
|
+
readonly description: "Select the previous search match";
|
|
227
|
+
};
|
|
228
|
+
readonly "tui.altScreen.searchClose": {
|
|
229
|
+
readonly defaultKeys: "escape";
|
|
230
|
+
readonly description: "Close transcript search";
|
|
231
|
+
};
|
|
208
232
|
readonly "tui.altScreen.top": {
|
|
209
233
|
readonly defaultKeys: "home";
|
|
210
234
|
readonly description: "Scroll viewport to top";
|
|
@@ -98,6 +98,7 @@ declare const ModelDefinitionSchema: Type.TObject<{
|
|
|
98
98
|
supportsLongCacheRetention: Type.TOptional<Type.TBoolean>;
|
|
99
99
|
supportsStrictMode: Type.TOptional<Type.TBoolean>;
|
|
100
100
|
supportsOpenAIGrammarTools: Type.TOptional<Type.TBoolean>;
|
|
101
|
+
supportsAdditionalTools: Type.TOptional<Type.TBoolean>;
|
|
101
102
|
supportsToolSearch: Type.TOptional<Type.TBoolean>;
|
|
102
103
|
}>, Type.TObject<{
|
|
103
104
|
supportsEagerToolInputStreaming: Type.TOptional<Type.TBoolean>;
|
|
@@ -206,6 +207,7 @@ declare const ModelOverrideSchema: Type.TObject<{
|
|
|
206
207
|
supportsLongCacheRetention: Type.TOptional<Type.TBoolean>;
|
|
207
208
|
supportsStrictMode: Type.TOptional<Type.TBoolean>;
|
|
208
209
|
supportsOpenAIGrammarTools: Type.TOptional<Type.TBoolean>;
|
|
210
|
+
supportsAdditionalTools: Type.TOptional<Type.TBoolean>;
|
|
209
211
|
supportsToolSearch: Type.TOptional<Type.TBoolean>;
|
|
210
212
|
}>, Type.TObject<{
|
|
211
213
|
supportsEagerToolInputStreaming: Type.TOptional<Type.TBoolean>;
|
|
@@ -292,6 +294,7 @@ declare const ProviderConfigSchema: Type.TObject<{
|
|
|
292
294
|
supportsLongCacheRetention: Type.TOptional<Type.TBoolean>;
|
|
293
295
|
supportsStrictMode: Type.TOptional<Type.TBoolean>;
|
|
294
296
|
supportsOpenAIGrammarTools: Type.TOptional<Type.TBoolean>;
|
|
297
|
+
supportsAdditionalTools: Type.TOptional<Type.TBoolean>;
|
|
295
298
|
supportsToolSearch: Type.TOptional<Type.TBoolean>;
|
|
296
299
|
}>, Type.TObject<{
|
|
297
300
|
supportsEagerToolInputStreaming: Type.TOptional<Type.TBoolean>;
|
|
@@ -403,6 +406,7 @@ declare const ProviderConfigSchema: Type.TObject<{
|
|
|
403
406
|
supportsLongCacheRetention: Type.TOptional<Type.TBoolean>;
|
|
404
407
|
supportsStrictMode: Type.TOptional<Type.TBoolean>;
|
|
405
408
|
supportsOpenAIGrammarTools: Type.TOptional<Type.TBoolean>;
|
|
409
|
+
supportsAdditionalTools: Type.TOptional<Type.TBoolean>;
|
|
406
410
|
supportsToolSearch: Type.TOptional<Type.TBoolean>;
|
|
407
411
|
}>, Type.TObject<{
|
|
408
412
|
supportsEagerToolInputStreaming: Type.TOptional<Type.TBoolean>;
|
|
@@ -511,6 +515,7 @@ declare const ProviderConfigSchema: Type.TObject<{
|
|
|
511
515
|
supportsLongCacheRetention: Type.TOptional<Type.TBoolean>;
|
|
512
516
|
supportsStrictMode: Type.TOptional<Type.TBoolean>;
|
|
513
517
|
supportsOpenAIGrammarTools: Type.TOptional<Type.TBoolean>;
|
|
518
|
+
supportsAdditionalTools: Type.TOptional<Type.TBoolean>;
|
|
514
519
|
supportsToolSearch: Type.TOptional<Type.TBoolean>;
|
|
515
520
|
}>, Type.TObject<{
|
|
516
521
|
supportsEagerToolInputStreaming: Type.TOptional<Type.TBoolean>;
|
|
@@ -96,6 +96,7 @@ const OpenAIResponsesCompatSchema = Type.Object({
|
|
|
96
96
|
supportsLongCacheRetention: Type.Optional(Type.Boolean()),
|
|
97
97
|
supportsStrictMode: Type.Optional(Type.Boolean()),
|
|
98
98
|
supportsOpenAIGrammarTools: Type.Optional(Type.Boolean()),
|
|
99
|
+
supportsAdditionalTools: Type.Optional(Type.Boolean()),
|
|
99
100
|
supportsToolSearch: Type.Optional(Type.Boolean()),
|
|
100
101
|
});
|
|
101
102
|
const AnthropicMessagesCompatSchema = Type.Object({
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { CONFIG_DIR_NAME } from "../config.js";
|
|
1
|
+
import { APP_NAME, CONFIG_DIR_NAME } from "../config.js";
|
|
2
2
|
import { emitProjectTrustEvent } from "./extensions/runner.js";
|
|
3
3
|
import { getProjectTrustOptions, hasTrustRequiringProjectResources, } from "./trust-manager.js";
|
|
4
4
|
function formatProjectTrustPrompt(cwd) {
|
|
5
|
-
return `Trust project folder?\n${cwd}\n\nThis allows
|
|
5
|
+
return `Trust project folder?\n${cwd}\n\nThis allows ${APP_NAME} to load ${CONFIG_DIR_NAME} settings and resources, install missing project packages, and execute project extensions.`;
|
|
6
6
|
}
|
|
7
7
|
async function selectProjectTrustOption(cwd, ctx) {
|
|
8
8
|
const options = getProjectTrustOptions(cwd, { includeSessionOnly: true });
|
package/dist/core/sdk.d.ts
CHANGED
|
@@ -34,9 +34,11 @@ export interface CreateAgentSessionOptions {
|
|
|
34
34
|
/**
|
|
35
35
|
* Optional allowlist of tool names.
|
|
36
36
|
*
|
|
37
|
-
* When omitted, pi
|
|
38
|
-
*
|
|
39
|
-
*
|
|
37
|
+
* When omitted, pi uses the `defaultTools` setting for the initial built-in
|
|
38
|
+
* selection when configured. Otherwise it enables the default built-in tools
|
|
39
|
+
* (read, bash, edit, write). Extension/custom tools remain enabled unless
|
|
40
|
+
* `noTools` changes that default. When provided, only the listed tool names are
|
|
41
|
+
* enabled.
|
|
40
42
|
*/
|
|
41
43
|
tools?: string[];
|
|
42
44
|
/** Optional denylist of tool names to disable. Applies after `tools` when both are provided. */
|
package/dist/core/sdk.js
CHANGED
|
@@ -130,10 +130,11 @@ export async function createAgentSession(options = {}) {
|
|
|
130
130
|
thinkingLevel = clampThinkingLevel(model, thinkingLevel);
|
|
131
131
|
}
|
|
132
132
|
const defaultActiveToolNames = ["read", "bash", "edit", "write"];
|
|
133
|
+
const configuredDefaultToolNames = settingsManager.getDefaultTools();
|
|
133
134
|
const allowedToolNames = options.tools ?? (options.noTools === "all" ? [] : undefined);
|
|
134
135
|
const excludedToolNames = options.excludeTools;
|
|
135
136
|
const excludedToolNameSet = excludedToolNames ? new Set(excludedToolNames) : undefined;
|
|
136
|
-
const initialActiveToolNames = (options.tools
|
|
137
|
+
const initialActiveToolNames = (options.tools ?? (options.noTools ? [] : (configuredDefaultToolNames ?? defaultActiveToolNames))).filter((name) => !excludedToolNameSet?.has(name));
|
|
137
138
|
let agent;
|
|
138
139
|
// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)
|
|
139
140
|
const convertToLlmWithBlockImages = (messages) => {
|
|
@@ -5,7 +5,7 @@ import { readdir, stat } from "fs/promises";
|
|
|
5
5
|
import { join, resolve } from "path";
|
|
6
6
|
import { createInterface } from "readline";
|
|
7
7
|
import { StringDecoder } from "string_decoder";
|
|
8
|
-
import { getAgentDir as getDefaultAgentDir, getSessionsDir } from "../config.js";
|
|
8
|
+
import { APP_NAME, getAgentDir as getDefaultAgentDir, getSessionsDir } from "../config.js";
|
|
9
9
|
import { normalizePath, resolvePath } from "../utils/paths.js";
|
|
10
10
|
import { createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage, } from "./messages.js";
|
|
11
11
|
export const CURRENT_SESSION_VERSION = 3;
|
|
@@ -620,7 +620,7 @@ export class SessionManager {
|
|
|
620
620
|
if (this.fileEntries.length === 0) {
|
|
621
621
|
const explicitPath = this.sessionFile;
|
|
622
622
|
if (statSync(explicitPath).size > 0) {
|
|
623
|
-
throw new Error(`Session file is not a valid
|
|
623
|
+
throw new Error(`Session file is not a valid ${APP_NAME} session: ${explicitPath}`);
|
|
624
624
|
}
|
|
625
625
|
this.newSession();
|
|
626
626
|
this.sessionFile = explicitPath;
|
|
@@ -22,6 +22,7 @@ export interface RetrySettings {
|
|
|
22
22
|
provider?: ProviderRetrySettings;
|
|
23
23
|
}
|
|
24
24
|
export type TuiMode = RendererTuiMode;
|
|
25
|
+
export type FullscreenExitOutput = "transcript" | "resume-hint";
|
|
25
26
|
export interface TerminalSettings {
|
|
26
27
|
showImages?: boolean;
|
|
27
28
|
imageWidthCells?: number;
|
|
@@ -97,6 +98,8 @@ export interface Settings {
|
|
|
97
98
|
agents?: string[];
|
|
98
99
|
terminal?: TerminalSettings;
|
|
99
100
|
tuiMode?: TuiMode;
|
|
101
|
+
defaultTools?: string[];
|
|
102
|
+
fullscreenExitOutput?: FullscreenExitOutput;
|
|
100
103
|
fullscreenScrollbar?: ScrollViewScrollbar;
|
|
101
104
|
images?: ImageSettings;
|
|
102
105
|
enabledModels?: string[];
|
|
@@ -288,11 +291,14 @@ export declare class SettingsManager {
|
|
|
288
291
|
setTuiMode(mode: TuiMode): void;
|
|
289
292
|
getFullscreenScrollbar(): ScrollViewScrollbar;
|
|
290
293
|
setFullscreenScrollbar(mode: ScrollViewScrollbar): void;
|
|
294
|
+
getFullscreenExitOutput(): FullscreenExitOutput;
|
|
295
|
+
setFullscreenExitOutput(output: FullscreenExitOutput): void;
|
|
291
296
|
getImageAutoResize(): boolean;
|
|
292
297
|
setImageAutoResize(enabled: boolean): void;
|
|
293
298
|
getBlockImages(): boolean;
|
|
294
299
|
setBlockImages(blocked: boolean): void;
|
|
295
300
|
getEnabledModels(): string[] | undefined;
|
|
301
|
+
getDefaultTools(): string[] | undefined;
|
|
296
302
|
setEnabledModels(patterns: string[] | undefined): void;
|
|
297
303
|
getDoubleEscapeAction(): "fork" | "tree" | "none";
|
|
298
304
|
setDoubleEscapeAction(action: "fork" | "tree" | "none"): void;
|
|
@@ -837,6 +837,14 @@ export class SettingsManager {
|
|
|
837
837
|
this.markModified("fullscreenScrollbar");
|
|
838
838
|
this.save();
|
|
839
839
|
}
|
|
840
|
+
getFullscreenExitOutput() {
|
|
841
|
+
return this.settings.fullscreenExitOutput === "resume-hint" ? "resume-hint" : "transcript";
|
|
842
|
+
}
|
|
843
|
+
setFullscreenExitOutput(output) {
|
|
844
|
+
this.globalSettings.fullscreenExitOutput = output;
|
|
845
|
+
this.markModified("fullscreenExitOutput");
|
|
846
|
+
this.save();
|
|
847
|
+
}
|
|
840
848
|
getImageAutoResize() {
|
|
841
849
|
return this.settings.images?.autoResize ?? true;
|
|
842
850
|
}
|
|
@@ -862,6 +870,10 @@ export class SettingsManager {
|
|
|
862
870
|
getEnabledModels() {
|
|
863
871
|
return this.settings.enabledModels;
|
|
864
872
|
}
|
|
873
|
+
getDefaultTools() {
|
|
874
|
+
const tools = this.settings.defaultTools;
|
|
875
|
+
return tools ? [...tools] : undefined;
|
|
876
|
+
}
|
|
865
877
|
setEnabledModels(patterns) {
|
|
866
878
|
this.globalSettings.enabledModels = patterns;
|
|
867
879
|
this.markModified("enabledModels");
|
|
@@ -51,7 +51,7 @@ export function buildSystemPrompt(options) {
|
|
|
51
51
|
if (activeGuidelines.length > 0) {
|
|
52
52
|
prompt += `\n\nActive tool guidelines:\n${activeGuidelines.map((guideline) => `- ${guideline}`).join("\n")}`;
|
|
53
53
|
}
|
|
54
|
-
prompt += `\nCurrent working directory: ${promptCwd}`;
|
|
54
|
+
prompt += `\nCurrent working directory: ${promptCwd}\n`;
|
|
55
55
|
return prompt;
|
|
56
56
|
}
|
|
57
57
|
// Get absolute paths to documentation and examples
|
package/dist/core/tools/bash.js
CHANGED
|
@@ -7,6 +7,7 @@ import { keyHint } from "../../modes/interactive/components/keybinding-hints.js"
|
|
|
7
7
|
import { truncateToVisualLines } from "../../modes/interactive/components/visual-truncate.js";
|
|
8
8
|
import { theme } from "../../modes/interactive/theme/theme.js";
|
|
9
9
|
import { waitForChildProcess } from "../../utils/child-process.js";
|
|
10
|
+
import { getExperimentalToolSampling } from "../experimental.js";
|
|
10
11
|
import { getShellConfig, getShellEnv, killProcessTree, trackDetachedChildPid, untrackDetachedChildPid, } from "../../utils/shell.js";
|
|
11
12
|
import { OutputAccumulator } from "./output-accumulator.js";
|
|
12
13
|
import { getTextOutput, invalidArgText, str } from "./render-utils.js";
|
|
@@ -232,6 +233,7 @@ export function createBashToolDefinition(cwd, options) {
|
|
|
232
233
|
? ["Inspect PI_* environment variables for current model and session details."]
|
|
233
234
|
: undefined,
|
|
234
235
|
parameters: bashSchema,
|
|
236
|
+
constrainedSampling: getExperimentalToolSampling(),
|
|
235
237
|
async execute(_toolCallId, { command, timeout }, signal, onUpdate, ctx) {
|
|
236
238
|
const resolvedCommand = commandPrefix ? `${commandPrefix}\n${command}` : command;
|
|
237
239
|
const spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook, exposeSessionEnvironment, ctx);
|
package/dist/core/tools/edit.js
CHANGED
|
@@ -3,6 +3,7 @@ import { constants } from "fs";
|
|
|
3
3
|
import { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from "fs/promises";
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
import { renderDiff } from "../../modes/interactive/components/diff.js";
|
|
6
|
+
import { getExperimentalToolSampling } from "../experimental.js";
|
|
6
7
|
import { applyEditsToNormalizedContent, computeEditsDiff, detectLineEnding, generateDiffString, generateUnifiedPatch, normalizeToLF, restoreLineEndings, stripBom, } from "./edit-diff.js";
|
|
7
8
|
import { withFileMutationQueue } from "./file-mutation-queue.js";
|
|
8
9
|
import { resolveToCwd } from "./path-utils.js";
|
|
@@ -169,6 +170,7 @@ export function createEditToolDefinition(cwd, options) {
|
|
|
169
170
|
"Keep edits[].oldText as small as possible while still being unique in the file. Do not pad with large unchanged regions.",
|
|
170
171
|
],
|
|
171
172
|
parameters: editSchema,
|
|
173
|
+
constrainedSampling: getExperimentalToolSampling(),
|
|
172
174
|
renderShell: "self",
|
|
173
175
|
prepareArguments: prepareEditArguments,
|
|
174
176
|
async execute(_toolCallId, input, signal, _onUpdate, _ctx) {
|
package/dist/core/tools/find.js
CHANGED
|
@@ -154,7 +154,7 @@ export function createFindToolDefinition(cwd, options) {
|
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
156
|
// Default implementation uses fd.
|
|
157
|
-
const fdPath = await ensureTool("fd"
|
|
157
|
+
const fdPath = await ensureTool("fd");
|
|
158
158
|
if (signal?.aborted) {
|
|
159
159
|
settle(() => reject(new Error("Operation aborted")));
|
|
160
160
|
return;
|
package/dist/core/tools/grep.js
CHANGED
|
@@ -92,7 +92,7 @@ export function createGrepToolDefinition(cwd, options) {
|
|
|
92
92
|
};
|
|
93
93
|
(async () => {
|
|
94
94
|
try {
|
|
95
|
-
const rgPath = await ensureTool("rg"
|
|
95
|
+
const rgPath = await ensureTool("rg");
|
|
96
96
|
if (!rgPath) {
|
|
97
97
|
settle(() => reject(new Error("ripgrep (rg) is not available and could not be downloaded")));
|
|
98
98
|
return;
|
package/dist/core/tools/read.js
CHANGED
|
@@ -9,6 +9,7 @@ import { getLanguageFromPath, highlightCode } from "../../modes/interactive/them
|
|
|
9
9
|
import { processImage } from "../../utils/image-process.js";
|
|
10
10
|
import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime.js";
|
|
11
11
|
import { formatPathRelativeToCwdOrAbsolute } from "../../utils/paths.js";
|
|
12
|
+
import { getExperimentalToolSampling } from "../experimental.js";
|
|
12
13
|
import { resolveReadPathAsync, resolveToCwd } from "./path-utils.js";
|
|
13
14
|
import { getTextOutput, renderToolPath, replaceTabs, str } from "./render-utils.js";
|
|
14
15
|
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
@@ -137,6 +138,7 @@ export function createReadToolDefinition(cwd, options) {
|
|
|
137
138
|
promptSnippet: "Read file contents",
|
|
138
139
|
promptGuidelines: ["Use read to examine files instead of cat or sed."],
|
|
139
140
|
parameters: readSchema,
|
|
141
|
+
constrainedSampling: getExperimentalToolSampling(),
|
|
140
142
|
async execute(_toolCallId, { path, offset, limit }, signal, _onUpdate, ctx) {
|
|
141
143
|
return new Promise((resolve, reject) => {
|
|
142
144
|
if (signal?.aborted) {
|
package/dist/core/tools/write.js
CHANGED
|
@@ -4,6 +4,7 @@ import { dirname } from "path";
|
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
|
|
6
6
|
import { getLanguageFromPath, highlightCode } from "../../modes/interactive/theme/theme.js";
|
|
7
|
+
import { getExperimentalToolSampling } from "../experimental.js";
|
|
7
8
|
import { withFileMutationQueue } from "./file-mutation-queue.js";
|
|
8
9
|
import { resolveToCwd } from "./path-utils.js";
|
|
9
10
|
import { normalizeDisplayText, renderToolPath, replaceTabs, str } from "./render-utils.js";
|
|
@@ -137,6 +138,7 @@ export function createWriteToolDefinition(cwd, options) {
|
|
|
137
138
|
promptSnippet: "Create or overwrite files",
|
|
138
139
|
promptGuidelines: ["Use write only for new files or complete rewrites."],
|
|
139
140
|
parameters: writeSchema,
|
|
141
|
+
constrainedSampling: getExperimentalToolSampling(),
|
|
140
142
|
async execute(_toolCallId, { path, content }, signal, _onUpdate, _ctx) {
|
|
141
143
|
const absolutePath = resolveToCwd(path, cwd);
|
|
142
144
|
const dir = dirname(absolutePath);
|
|
@@ -29,7 +29,6 @@ const WATCHER_RESTART_DELAY_MS = 3000;
|
|
|
29
29
|
const POLL_INTERVAL_MS = 3000;
|
|
30
30
|
const HEALTHY_SCAN_INTERVAL_MS = 60_000;
|
|
31
31
|
const RETRY_DELAY_MS = 100;
|
|
32
|
-
const SLOW_RESULT_SCAN_MS = 500;
|
|
33
32
|
|
|
34
33
|
type ResultWatcherFs = Pick<typeof fs, "existsSync" | "readFileSync" | "unlinkSync" | "readdirSync" | "mkdirSync" | "realpathSync" | "statSync" | "watch">;
|
|
35
34
|
|
|
@@ -92,12 +91,6 @@ type ResultFileIdentity = {
|
|
|
92
91
|
asyncDir?: string;
|
|
93
92
|
};
|
|
94
93
|
|
|
95
|
-
interface ResultScanStats {
|
|
96
|
-
files: number;
|
|
97
|
-
scheduled: number;
|
|
98
|
-
startedAt: number;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
94
|
function jsonStringProperty(raw: string, property: string): string | undefined {
|
|
102
95
|
const matches = raw.matchAll(new RegExp(`"${property}"\\s*:\\s*("(?:\\\\.|[^"\\\\])*")`, "g"));
|
|
103
96
|
let encoded: string | undefined;
|
|
@@ -547,11 +540,6 @@ export function createResultWatcher(
|
|
|
547
540
|
void handleResult(file, triggerTurn);
|
|
548
541
|
}, deps.coalesceDelayMs ?? 50);
|
|
549
542
|
|
|
550
|
-
const logScanStats = (stats: ResultScanStats) => {
|
|
551
|
-
const elapsed = Date.now() - stats.startedAt;
|
|
552
|
-
if (elapsed < SLOW_RESULT_SCAN_MS) return;
|
|
553
|
-
console.error(`Subagent result scan inspected ${stats.files} indexed result file(s), scheduled ${stats.scheduled} in ${elapsed}ms (${resultsDir}).`);
|
|
554
|
-
};
|
|
555
543
|
const indexedResultCandidates = (observed: ReadonlySet<string>): string[] => {
|
|
556
544
|
const files = new Set<string>();
|
|
557
545
|
if (state.currentSessionId) {
|
|
@@ -565,14 +553,11 @@ export function createResultWatcher(
|
|
|
565
553
|
const primeExistingResults = (options: { triggerTurn?: boolean } = {}) => {
|
|
566
554
|
try {
|
|
567
555
|
const triggerTurn = options.triggerTurn !== false;
|
|
568
|
-
const stats: ResultScanStats = { files: 0, scheduled: 0, startedAt: Date.now() };
|
|
569
556
|
const observed = observedRunIds();
|
|
570
557
|
for (const file of indexedResultCandidates(observed)) {
|
|
571
|
-
stats.files += 1;
|
|
572
558
|
const signature = resultSignature(file, observed);
|
|
573
559
|
if (!signature) continue;
|
|
574
560
|
if (!shouldProcessResult(file, observed, signature)) continue;
|
|
575
|
-
stats.scheduled += 1;
|
|
576
561
|
scheduleResult(file, triggerTurn);
|
|
577
562
|
}
|
|
578
563
|
// Selesai fork: during the lineage adoption window, also scan flat
|
|
@@ -584,15 +569,12 @@ export function createResultWatcher(
|
|
|
584
569
|
const indexed = indexedResultCandidates(observed);
|
|
585
570
|
for (const file of fsApi.readdirSync(resultsDir)) {
|
|
586
571
|
if (!file.endsWith(".json") || indexed.includes(file)) continue;
|
|
587
|
-
stats.files += 1;
|
|
588
572
|
const signature = resultSignature(file, observed);
|
|
589
573
|
if (!signature) continue;
|
|
590
574
|
if (!shouldProcessResult(file, observed, signature)) continue;
|
|
591
|
-
stats.scheduled += 1;
|
|
592
575
|
scheduleResult(file, triggerTurn);
|
|
593
576
|
}
|
|
594
577
|
}
|
|
595
|
-
logScanStats(stats);
|
|
596
578
|
} catch (error) {
|
|
597
579
|
if (!isNotFound(error)) console.error(`Failed to scan subagent result index in '${resultsDir}':`, error);
|
|
598
580
|
}
|
|
@@ -126,6 +126,32 @@ describe("result watcher", () => {
|
|
|
126
126
|
}
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
+
it("keeps slow indexed result scans silent", () => {
|
|
130
|
+
const resultsDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-result-watcher-slow-scan-"));
|
|
131
|
+
const originalNow = Date.now;
|
|
132
|
+
const originalError = console.error;
|
|
133
|
+
const logged: unknown[][] = [];
|
|
134
|
+
try {
|
|
135
|
+
const state = createState();
|
|
136
|
+
state.currentSessionId = "session-current";
|
|
137
|
+
state.asyncJobs.set("slow-scan", { asyncId: "slow-scan", asyncDir: path.join(resultsDir, "slow-scan"), status: "running", startedAt: 0, updatedAt: 0 });
|
|
138
|
+
let nowCalls = 0;
|
|
139
|
+
Date.now = () => nowCalls++ === 0 ? 0 : 501;
|
|
140
|
+
console.error = (...args: unknown[]) => { logged.push(args); };
|
|
141
|
+
const watcher = createResultWatcher({ events: { on: () => () => {}, emit() {} } }, state, resultsDir, 60_000);
|
|
142
|
+
try {
|
|
143
|
+
watcher.primeExistingResults();
|
|
144
|
+
} finally {
|
|
145
|
+
watcher.stopResultWatcher();
|
|
146
|
+
}
|
|
147
|
+
assert.deepEqual(logged, []);
|
|
148
|
+
} finally {
|
|
149
|
+
Date.now = originalNow;
|
|
150
|
+
console.error = originalError;
|
|
151
|
+
fs.rmSync(resultsDir, { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
129
155
|
it("skips full parsing for unrelated sessions during priming, safety scans, and native watch events", async () => {
|
|
130
156
|
const resultsDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-result-watcher-filter-"));
|
|
131
157
|
try {
|
|
@@ -15,7 +15,7 @@ import { getAgentDir, getModelsPath } from "@selesai/code";
|
|
|
15
15
|
import type { AuthStorage, ExtensionAPI, ExtensionCommandContext, ExtensionContext, SessionStartEvent } from "@selesai/code";
|
|
16
16
|
|
|
17
17
|
export const TOKEN_IN_PROVIDER = "tokenin";
|
|
18
|
-
export const TOKEN_IN_DASHBOARD_URL = "https://token
|
|
18
|
+
export const TOKEN_IN_DASHBOARD_URL = "https://token.selesai.in/dashboard/tokens";
|
|
19
19
|
export const ONBOARDING_MARKER_NAME = ".tokenInOnboardingComplete";
|
|
20
20
|
export const PLACEHOLDER_API_KEY = "sk-xxx";
|
|
21
21
|
|
package/dist/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type { ResourceCollision, ResourceDiagnostic, ResourceLoader } from "./co
|
|
|
18
18
|
export { DefaultResourceLoader, loadProjectContextFiles } from "./core/resource-loader.ts";
|
|
19
19
|
export { AgentSessionRuntime, type AgentSessionRuntimeDiagnostic, type AgentSessionServices, type CreateAgentSessionFromServicesOptions, type CreateAgentSessionOptions, type CreateAgentSessionResult, type CreateAgentSessionRuntimeFactory, type CreateAgentSessionRuntimeResult, type CreateAgentSessionServicesOptions, createAgentSession, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createBashTool, createCodingTools, createEditTool, createFindTool, createGrepTool, createLsTool, createReadOnlyTools, createReadTool, createWriteTool, type PromptTemplate, } from "./core/sdk.ts";
|
|
20
20
|
export { type BranchSummaryEntry, buildContextEntries, buildSessionContext, type CompactionEntry, CURRENT_SESSION_VERSION, type CustomEntry, type CustomMessageEntry, type FileEntry, getLatestCompactionEntry, type ModelChangeEntry, migrateSessionEntries, type NewSessionOptions, parseSessionEntries, type SessionContext, type SessionEntry, type SessionEntryBase, type SessionHeader, type SessionInfo, type SessionInfoEntry, SessionManager, type SessionMessageEntry, type SessionTreeNode, sessionEntryToContextMessages, type ThinkingLevelChangeEntry, } from "./core/session-manager.ts";
|
|
21
|
-
export { type AutoHandoffSettings, type CompactionSettings, type DefaultProjectTrust, type ImageSettings, type PackageSource, type RetrySettings, SettingsManager, type SettingsManagerCreateOptions, type TuiMode, } from "./core/settings-manager.ts";
|
|
21
|
+
export { type AutoHandoffSettings, type CompactionSettings, type DefaultProjectTrust, type FullscreenExitOutput, type ImageSettings, type PackageSource, type RetrySettings, SettingsManager, type SettingsManagerCreateOptions, type TuiMode, } from "./core/settings-manager.ts";
|
|
22
22
|
export { formatSkillsForPrompt, type LoadSkillsFromDirOptions, type LoadSkillsResult, loadSkills, loadSkillsFromDir, type Skill, type SkillFrontmatter, } from "./core/skills.ts";
|
|
23
23
|
export { createSyntheticSourceInfo } from "./core/source-info.ts";
|
|
24
24
|
export { type EditDiffResult, generateDiffString, generateUnifiedPatch } from "./core/tools/edit-diff.ts";
|
package/dist/main.js
CHANGED
|
@@ -491,6 +491,9 @@ export async function main(args, options) {
|
|
|
491
491
|
await showFirstTimeSetup(startupSettingsManager);
|
|
492
492
|
time("firstTimeSetup");
|
|
493
493
|
}
|
|
494
|
+
if (appMode === "interactive" && parsed.useTheme !== undefined) {
|
|
495
|
+
startupSettingsManager.applyOverrides({ theme: parsed.useTheme });
|
|
496
|
+
}
|
|
494
497
|
// Decide the final runtime cwd before creating cwd-bound runtime services.
|
|
495
498
|
// --session and --resume may select a session from another project, so project-local
|
|
496
499
|
// settings, resources, provider registrations, and models must be resolved only after
|
|
@@ -723,6 +726,7 @@ export async function main(args, options) {
|
|
|
723
726
|
initialMessages: parsed.messages,
|
|
724
727
|
verbose: parsed.verbose,
|
|
725
728
|
tuiMode: parsed.tuiMode,
|
|
729
|
+
initialThemeSetting: parsed.useTheme,
|
|
726
730
|
});
|
|
727
731
|
if (startupBenchmark) {
|
|
728
732
|
await interactiveMode.init();
|