@oh-my-pi/pi-coding-agent 15.1.2 → 15.1.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 +42 -0
- package/dist/types/cli/auth-broker-cli.d.ts +25 -0
- package/dist/types/cli/auth-gateway-cli.d.ts +18 -0
- package/dist/types/cli/grievances-cli.d.ts +12 -0
- package/dist/types/commands/auth-broker.d.ts +54 -0
- package/dist/types/commands/auth-gateway.d.ts +32 -0
- package/dist/types/commands/grievances.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +9 -1
- package/dist/types/commit/agentic/tools/schemas.d.ts +9 -1
- package/dist/types/commit/agentic/tools/split-commit.d.ts +9 -1
- package/dist/types/config/model-registry.d.ts +3 -0
- package/dist/types/config/models-config-schema.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +46 -0
- package/dist/types/discovery/agents.d.ts +12 -1
- package/dist/types/edit/renderer.d.ts +3 -0
- package/dist/types/eval/index.d.ts +0 -2
- package/dist/types/goals/tools/goal-tool.d.ts +10 -2
- package/dist/types/index.d.ts +0 -1
- package/dist/types/internal-urls/index.d.ts +1 -1
- package/dist/types/internal-urls/{pi-protocol.d.ts → omp-protocol.d.ts} +3 -3
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/modes/acp/acp-agent.d.ts +1 -0
- package/dist/types/modes/emoji-autocomplete.d.ts +16 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +4 -0
- package/dist/types/plan-mode/approved-plan.d.ts +4 -0
- package/dist/types/sdk.d.ts +10 -3
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/auth-broker-config.d.ts +13 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/tools/eval.d.ts +41 -7
- package/dist/types/tools/irc.d.ts +8 -2
- package/dist/types/tools/report-tool-issue.d.ts +118 -1
- package/dist/types/tools/resolve.d.ts +8 -2
- package/examples/custom-tools/README.md +3 -12
- package/examples/extensions/README.md +2 -15
- package/examples/extensions/api-demo.ts +1 -7
- package/package.json +7 -7
- package/src/autoresearch/tools/init-experiment.ts +11 -33
- package/src/autoresearch/tools/log-experiment.ts +10 -24
- package/src/autoresearch/tools/run-experiment.ts +1 -1
- package/src/autoresearch/tools/update-notes.ts +2 -9
- package/src/cli/auth-broker-cli.ts +746 -0
- package/src/cli/auth-gateway-cli.ts +342 -0
- package/src/cli/grievances-cli.ts +109 -16
- package/src/cli.ts +4 -2
- package/src/commands/auth-broker.ts +96 -0
- package/src/commands/auth-gateway.ts +61 -0
- package/src/commands/grievances.ts +13 -8
- package/src/commands/launch.ts +1 -1
- package/src/commit/agentic/agent.ts +2 -0
- package/src/commit/agentic/tools/analyze-file.ts +2 -2
- package/src/commit/agentic/tools/git-file-diff.ts +2 -2
- package/src/commit/agentic/tools/git-hunk.ts +3 -3
- package/src/commit/agentic/tools/git-overview.ts +2 -2
- package/src/commit/agentic/tools/propose-changelog.ts +1 -3
- package/src/commit/agentic/tools/recent-commits.ts +1 -1
- package/src/commit/agentic/tools/schemas.ts +1 -9
- package/src/config/model-equivalence.ts +279 -174
- package/src/config/model-registry.ts +37 -6
- package/src/config/model-resolver.ts +13 -8
- package/src/config/models-config-schema.ts +8 -0
- package/src/config/settings-schema.ts +52 -0
- package/src/cursor.ts +1 -1
- package/src/debug/log-formatting.ts +1 -1
- package/src/debug/log-viewer.ts +1 -1
- package/src/debug/profiler.ts +4 -0
- package/src/debug/raw-sse-buffer.ts +100 -59
- package/src/debug/raw-sse.ts +1 -1
- package/src/discovery/agents.ts +15 -4
- package/src/edit/modes/apply-patch.ts +1 -5
- package/src/edit/modes/patch.ts +5 -5
- package/src/edit/modes/replace.ts +5 -5
- package/src/edit/renderer.ts +2 -1
- package/src/edit/streaming.ts +1 -1
- package/src/eval/index.ts +0 -2
- package/src/eval/js/shared/runtime.ts +25 -0
- package/src/eval/py/kernel.ts +1 -1
- package/src/exa/researcher.ts +4 -4
- package/src/exa/search.ts +10 -22
- package/src/exa/websets.ts +33 -33
- package/src/goals/tools/goal-tool.ts +3 -3
- package/src/index.ts +0 -3
- package/src/internal-urls/docs-index.generated.ts +21 -18
- package/src/internal-urls/index.ts +1 -1
- package/src/internal-urls/{pi-protocol.ts → omp-protocol.ts} +10 -10
- package/src/internal-urls/router.ts +3 -3
- package/src/internal-urls/types.ts +1 -1
- package/src/lsp/types.ts +8 -11
- package/src/main.ts +3 -0
- package/src/mcp/tool-bridge.ts +3 -3
- package/src/modes/acp/acp-agent.ts +88 -25
- package/src/modes/components/bash-execution.ts +1 -1
- package/src/modes/components/diff.ts +1 -2
- package/src/modes/components/eval-execution.ts +1 -1
- package/src/modes/components/oauth-selector.ts +38 -2
- package/src/modes/components/tool-execution.ts +1 -2
- package/src/modes/controllers/command-controller.ts +95 -34
- package/src/modes/controllers/input-controller.ts +4 -3
- package/src/modes/data/emojis.json +1 -0
- package/src/modes/emoji-autocomplete.ts +285 -0
- package/src/modes/interactive-mode.ts +92 -19
- package/src/modes/print-mode.ts +3 -3
- package/src/modes/prompt-action-autocomplete.ts +14 -0
- package/src/plan-mode/approved-plan.ts +9 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/ttsr-tool-reminder.md +5 -0
- package/src/prompts/tools/eval.md +25 -26
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/resolve.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/web-search.md +1 -1
- package/src/sdk.ts +78 -7
- package/src/session/agent-session.ts +176 -77
- package/src/session/agent-storage.ts +7 -2
- package/src/session/auth-broker-config.ts +102 -0
- package/src/session/auth-storage.ts +7 -1
- package/src/session/streaming-output.ts +1 -1
- package/src/task/types.ts +10 -35
- package/src/tools/bash-interactive.ts +4 -1
- package/src/tools/bash-pty-selection.ts +2 -2
- package/src/tools/browser.ts +12 -20
- package/src/tools/eval.ts +77 -100
- package/src/tools/gh.ts +21 -45
- package/src/tools/hindsight-recall.ts +1 -1
- package/src/tools/hindsight-reflect.ts +2 -2
- package/src/tools/hindsight-retain.ts +3 -7
- package/src/tools/index.ts +8 -1
- package/src/tools/inspect-image.ts +4 -1
- package/src/tools/irc.ts +4 -12
- package/src/tools/job.ts +3 -11
- package/src/tools/report-tool-issue.ts +462 -17
- package/src/tools/resolve.ts +2 -7
- package/src/tools/todo-write.ts +8 -15
- package/src/utils/title-generator.ts +3 -0
- package/src/web/search/index.ts +6 -6
- package/dist/types/eval/parse.d.ts +0 -28
- package/dist/types/eval/sniff.d.ts +0 -11
- package/src/eval/eval.lark +0 -36
- package/src/eval/parse.ts +0 -407
- package/src/eval/sniff.ts +0 -28
|
@@ -15,8 +15,8 @@ export * from "./json-query";
|
|
|
15
15
|
export * from "./local-protocol";
|
|
16
16
|
export * from "./mcp-protocol";
|
|
17
17
|
export * from "./memory-protocol";
|
|
18
|
+
export * from "./omp-protocol";
|
|
18
19
|
export * from "./parse";
|
|
19
|
-
export * from "./pi-protocol";
|
|
20
20
|
export * from "./router";
|
|
21
21
|
export * from "./rule-protocol";
|
|
22
22
|
export * from "./skill-protocol";
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Protocol handler for
|
|
2
|
+
* Protocol handler for omp:// URLs.
|
|
3
3
|
*
|
|
4
4
|
* Serves statically embedded documentation files bundled at build time.
|
|
5
5
|
*
|
|
6
6
|
* URL forms:
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
7
|
+
* - omp:// - Lists all available documentation files
|
|
8
|
+
* - omp://<file>.md - Reads a specific documentation file
|
|
9
9
|
*/
|
|
10
10
|
import * as path from "node:path";
|
|
11
11
|
import { EMBEDDED_DOC_FILENAMES, EMBEDDED_DOCS } from "./docs-index.generated";
|
|
12
12
|
import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Handler for
|
|
15
|
+
* Handler for omp:// URLs.
|
|
16
16
|
*
|
|
17
17
|
* Resolves documentation file names to their content, or lists available docs.
|
|
18
18
|
*/
|
|
19
|
-
export class
|
|
20
|
-
readonly scheme = "
|
|
19
|
+
export class OmpProtocolHandler implements ProtocolHandler {
|
|
20
|
+
readonly scheme = "omp";
|
|
21
21
|
readonly immutable = true;
|
|
22
22
|
|
|
23
23
|
async resolve(url: InternalUrl): Promise<InternalResource> {
|
|
@@ -38,7 +38,7 @@ export class PiProtocolHandler implements ProtocolHandler {
|
|
|
38
38
|
throw new Error("No documentation files found");
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
const listing = EMBEDDED_DOC_FILENAMES.map(f => `- [${f}](
|
|
41
|
+
const listing = EMBEDDED_DOC_FILENAMES.map(f => `- [${f}](omp://${f})`).join("\n");
|
|
42
42
|
const content = `# Documentation\n\n${EMBEDDED_DOC_FILENAMES.length} files available:\n\n${listing}\n`;
|
|
43
43
|
|
|
44
44
|
return {
|
|
@@ -52,12 +52,12 @@ export class PiProtocolHandler implements ProtocolHandler {
|
|
|
52
52
|
async #readDoc(filename: string, url: InternalUrl): Promise<InternalResource> {
|
|
53
53
|
// Validate: no traversal, no absolute paths
|
|
54
54
|
if (path.isAbsolute(filename)) {
|
|
55
|
-
throw new Error("Absolute paths are not allowed in
|
|
55
|
+
throw new Error("Absolute paths are not allowed in omp:// URLs");
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
const normalized = path.posix.normalize(filename.replaceAll("\\", "/"));
|
|
59
59
|
if (normalized === ".." || normalized.startsWith("../") || normalized.includes("/../")) {
|
|
60
|
-
throw new Error("Path traversal (..) is not allowed in
|
|
60
|
+
throw new Error("Path traversal (..) is not allowed in omp:// URLs");
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
const content = EMBEDDED_DOCS[normalized];
|
|
@@ -69,7 +69,7 @@ export class PiProtocolHandler implements ProtocolHandler {
|
|
|
69
69
|
const suffix =
|
|
70
70
|
suggestions.length > 0
|
|
71
71
|
? `\nDid you mean: ${suggestions.join(", ")}`
|
|
72
|
-
: "\nUse
|
|
72
|
+
: "\nUse omp:// to list available files.";
|
|
73
73
|
throw new Error(`Documentation file not found: ${filename}${suffix}`);
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Internal URL router for internal protocols (agent://, artifact://, memory://, skill://, rule://, mcp://,
|
|
2
|
+
* Internal URL router for internal protocols (agent://, artifact://, memory://, skill://, rule://, mcp://, omp://, local://).
|
|
3
3
|
*
|
|
4
4
|
* One process-global router with one handler per scheme. Access via
|
|
5
5
|
* `InternalUrlRouter.instance()`. Handlers are stateless; per-session and
|
|
@@ -11,8 +11,8 @@ import { IssueProtocolHandler, PrProtocolHandler } from "./issue-pr-protocol";
|
|
|
11
11
|
import { LocalProtocolHandler } from "./local-protocol";
|
|
12
12
|
import { McpProtocolHandler } from "./mcp-protocol";
|
|
13
13
|
import { MemoryProtocolHandler } from "./memory-protocol";
|
|
14
|
+
import { OmpProtocolHandler } from "./omp-protocol";
|
|
14
15
|
import { parseInternalUrl } from "./parse";
|
|
15
|
-
import { PiProtocolHandler } from "./pi-protocol";
|
|
16
16
|
import { RuleProtocolHandler } from "./rule-protocol";
|
|
17
17
|
import { SkillProtocolHandler } from "./skill-protocol";
|
|
18
18
|
import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext } from "./types";
|
|
@@ -23,7 +23,7 @@ export class InternalUrlRouter {
|
|
|
23
23
|
#handlers = new Map<string, ProtocolHandler>();
|
|
24
24
|
|
|
25
25
|
constructor() {
|
|
26
|
-
this.register(new
|
|
26
|
+
this.register(new OmpProtocolHandler());
|
|
27
27
|
this.register(new AgentProtocolHandler());
|
|
28
28
|
this.register(new ArtifactProtocolHandler());
|
|
29
29
|
this.register(new MemoryProtocolHandler());
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Types for the internal URL routing system.
|
|
3
3
|
*
|
|
4
|
-
* Internal URLs (agent://, artifact://, memory://, skill://, rule://, mcp://,
|
|
4
|
+
* Internal URLs (agent://, artifact://, memory://, skill://, rule://, mcp://, omp://, local://) are resolved by tools like read,
|
|
5
5
|
* providing access to agent outputs and server resources without exposing filesystem paths.
|
|
6
6
|
*/
|
|
7
7
|
|
package/src/lsp/types.ts
CHANGED
|
@@ -22,17 +22,14 @@ export const lspSchema = z.object({
|
|
|
22
22
|
"capabilities",
|
|
23
23
|
"request",
|
|
24
24
|
]),
|
|
25
|
-
file: z.string().describe("
|
|
26
|
-
line: z.number().describe("
|
|
27
|
-
symbol: z.string().describe("
|
|
28
|
-
query: z.string().describe("
|
|
29
|
-
new_name: z.string().describe("
|
|
30
|
-
apply: z.boolean().describe("
|
|
31
|
-
timeout: z.number().describe("
|
|
32
|
-
payload: z
|
|
33
|
-
.string()
|
|
34
|
-
.describe("JSON-encoded params for action=request. When omitted, params are auto-built from file/line/symbol.")
|
|
35
|
-
.optional(),
|
|
25
|
+
file: z.string().describe("file path or source path for rename_file").optional(),
|
|
26
|
+
line: z.number().describe("line number (1-indexed)").optional(),
|
|
27
|
+
symbol: z.string().describe("symbol substring on the line").optional(),
|
|
28
|
+
query: z.string().describe("search query or code-action selector").optional(),
|
|
29
|
+
new_name: z.string().describe("new symbol name or destination path").optional(),
|
|
30
|
+
apply: z.boolean().describe("apply edits").optional(),
|
|
31
|
+
timeout: z.number().describe("request timeout in seconds").optional(),
|
|
32
|
+
payload: z.string().describe("json-encoded request params").optional(),
|
|
36
33
|
});
|
|
37
34
|
|
|
38
35
|
export type LspParams = z.infer<typeof lspSchema>;
|
package/src/main.ts
CHANGED
|
@@ -940,6 +940,9 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
940
940
|
initialMessage,
|
|
941
941
|
initialImages,
|
|
942
942
|
});
|
|
943
|
+
if ($env.PI_TIMING) {
|
|
944
|
+
logger.printTimings();
|
|
945
|
+
}
|
|
943
946
|
await session.dispose();
|
|
944
947
|
stopThemeWatcher();
|
|
945
948
|
await postmortem.quit(0);
|
package/src/mcp/tool-bridge.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
7
7
|
import type { TSchema } from "@oh-my-pi/pi-ai";
|
|
8
|
-
import {
|
|
8
|
+
import { normalizeSchemaForMCP } from "@oh-my-pi/pi-ai/utils/schema";
|
|
9
9
|
import { untilAborted } from "@oh-my-pi/pi-utils";
|
|
10
10
|
import type { SourceMeta } from "../capability/types";
|
|
11
11
|
import type {
|
|
@@ -231,7 +231,7 @@ export class MCPTool implements CustomTool<TSchema, MCPToolDetails> {
|
|
|
231
231
|
this.name = createMCPToolName(connection.name, tool.name);
|
|
232
232
|
this.label = `${connection.name}/${tool.name}`;
|
|
233
233
|
this.description = tool.description ?? `MCP tool from ${connection.name}`;
|
|
234
|
-
this.parameters =
|
|
234
|
+
this.parameters = normalizeSchemaForMCP(tool.inputSchema) as TSchema;
|
|
235
235
|
this.mcpToolName = tool.name;
|
|
236
236
|
this.mcpServerName = connection.name;
|
|
237
237
|
}
|
|
@@ -324,7 +324,7 @@ export class DeferredMCPTool implements CustomTool<TSchema, MCPToolDetails> {
|
|
|
324
324
|
this.name = createMCPToolName(serverName, tool.name);
|
|
325
325
|
this.label = `${serverName}/${tool.name}`;
|
|
326
326
|
this.description = tool.description ?? `MCP tool from ${serverName}`;
|
|
327
|
-
this.parameters =
|
|
327
|
+
this.parameters = normalizeSchemaForMCP(tool.inputSchema) as TSchema;
|
|
328
328
|
this.mcpToolName = tool.name;
|
|
329
329
|
this.mcpServerName = serverName;
|
|
330
330
|
this.#fallbackProvider = source?.provider;
|
|
@@ -86,6 +86,7 @@ const SESSION_PAGE_SIZE = 50;
|
|
|
86
86
|
* wait past this guard without hard-coding the literal.
|
|
87
87
|
*/
|
|
88
88
|
export const ACP_BOOTSTRAP_RACE_GUARD_MS = 50;
|
|
89
|
+
const ACP_CANCEL_CLEANUP_TIMEOUT_MS = 5_000;
|
|
89
90
|
|
|
90
91
|
type AgentImageContent = {
|
|
91
92
|
type: "image";
|
|
@@ -102,6 +103,13 @@ type PromptTurnState = {
|
|
|
102
103
|
userMessageId: string;
|
|
103
104
|
cancelRequested: boolean;
|
|
104
105
|
settled: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* `abort()` is in-flight (or its bounded-timeout race). `undefined` while the turn is
|
|
108
|
+
* running normally and after cleanup completes. The turn occupies `record.promptTurn`
|
|
109
|
+
* for as long as either `!settled` or `cleanup` is set — that combined window is the
|
|
110
|
+
* "turn in flight" predicate (`isPromptTurnInFlight`) every consumer gates on.
|
|
111
|
+
*/
|
|
112
|
+
cleanup: Promise<void> | undefined;
|
|
105
113
|
usageBaseline: UsageStatistics;
|
|
106
114
|
unsubscribe: (() => void) | undefined;
|
|
107
115
|
resolve: (value: PromptResponse) => void;
|
|
@@ -109,6 +117,16 @@ type PromptTurnState = {
|
|
|
109
117
|
promise: Promise<PromptResponse>;
|
|
110
118
|
};
|
|
111
119
|
|
|
120
|
+
/**
|
|
121
|
+
* A turn is "in flight" from the moment `prompt()` reserves the slot until `settled` is
|
|
122
|
+
* true AND any cancel cleanup has completed. Fork/queue/event gating all depend on this
|
|
123
|
+
* combined window — a settled-but-still-aborting turn is not safe to fork from, queue
|
|
124
|
+
* onto, or forward late events for.
|
|
125
|
+
*/
|
|
126
|
+
function isPromptTurnInFlight(turn: PromptTurnState | undefined): turn is PromptTurnState {
|
|
127
|
+
return turn !== undefined && (!turn.settled || turn.cleanup !== undefined);
|
|
128
|
+
}
|
|
129
|
+
|
|
112
130
|
type ManagedSessionRecord = {
|
|
113
131
|
session: AgentSession;
|
|
114
132
|
mcpManager: MCPManager | undefined;
|
|
@@ -337,6 +355,7 @@ export class AcpAgent implements Agent {
|
|
|
337
355
|
#disposePromise: Promise<void> | undefined;
|
|
338
356
|
#cleanupRegistered = false;
|
|
339
357
|
#clientCapabilities: ClientCapabilities | undefined;
|
|
358
|
+
#cancelCleanupTimeoutMs = ACP_CANCEL_CLEANUP_TIMEOUT_MS;
|
|
340
359
|
|
|
341
360
|
constructor(connection: AgentSideConnection, initialSession: AgentSession, createSession: CreateAcpSession) {
|
|
342
361
|
this.#connection = connection;
|
|
@@ -344,6 +363,10 @@ export class AcpAgent implements Agent {
|
|
|
344
363
|
this.#createSession = createSession;
|
|
345
364
|
}
|
|
346
365
|
|
|
366
|
+
setCancelCleanupTimeoutForTesting(timeoutMs: number): void {
|
|
367
|
+
this.#cancelCleanupTimeoutMs = Math.max(1, timeoutMs);
|
|
368
|
+
}
|
|
369
|
+
|
|
347
370
|
async initialize(params: InitializeRequest): Promise<InitializeResponse> {
|
|
348
371
|
this.#registerConnectionCleanup();
|
|
349
372
|
this.#clientCapabilities = params.clientCapabilities;
|
|
@@ -546,9 +569,15 @@ export class AcpAgent implements Agent {
|
|
|
546
569
|
throw new Error("ACP prompt already in progress for this session");
|
|
547
570
|
}
|
|
548
571
|
return await this.#queuePrompt(record, async () => {
|
|
549
|
-
const
|
|
550
|
-
if (
|
|
551
|
-
|
|
572
|
+
const previousTurn = record.promptTurn;
|
|
573
|
+
if (previousTurn) {
|
|
574
|
+
// Wait for any prompt that's still settling or whose cancel cleanup is
|
|
575
|
+
// still in flight. We deliberately swallow the prompt rejection (the
|
|
576
|
+
// owning caller already received it) but let cleanup rejections
|
|
577
|
+
// propagate — a timed-out cancel must fail this queued prompt instead
|
|
578
|
+
// of letting it run on a session that is about to be closed.
|
|
579
|
+
await previousTurn.promise.catch(() => undefined);
|
|
580
|
+
await previousTurn.cleanup;
|
|
552
581
|
}
|
|
553
582
|
|
|
554
583
|
const converted = this.#convertPromptBlocks(params.prompt);
|
|
@@ -557,6 +586,7 @@ export class AcpAgent implements Agent {
|
|
|
557
586
|
userMessageId: params.messageId ?? crypto.randomUUID(),
|
|
558
587
|
cancelRequested: false,
|
|
559
588
|
settled: false,
|
|
589
|
+
cleanup: undefined,
|
|
560
590
|
usageBaseline: this.#cloneUsageStatistics(record.session.sessionManager.getUsageStatistics()),
|
|
561
591
|
unsubscribe: undefined,
|
|
562
592
|
resolve: pendingPrompt.resolve,
|
|
@@ -676,16 +706,53 @@ export class AcpAgent implements Agent {
|
|
|
676
706
|
if (!promptTurn || promptTurn.settled) {
|
|
677
707
|
return;
|
|
678
708
|
}
|
|
679
|
-
|
|
709
|
+
const cleanup = this.#beginCancelCleanup(record, promptTurn);
|
|
680
710
|
try {
|
|
681
|
-
await
|
|
682
|
-
this.#finishPrompt(record, {
|
|
683
|
-
stopReason: "cancelled",
|
|
684
|
-
usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
|
|
685
|
-
userMessageId: promptTurn.userMessageId,
|
|
686
|
-
});
|
|
711
|
+
await cleanup;
|
|
687
712
|
} catch (error: unknown) {
|
|
688
|
-
|
|
713
|
+
logger.warn("ACP cancel cleanup timed out; closing session", { sessionId: record.session.sessionId, error });
|
|
714
|
+
await this.#closeManagedSession(record.session.sessionId, record);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Transition a still-running turn into cancellation: mark intent, drop the live-event
|
|
720
|
+
* subscription, start the bounded `abort()` race, and resolve the ACP prompt response
|
|
721
|
+
* with `stopReason: "cancelled"` so the client sees acceptance immediately. The
|
|
722
|
+
* returned promise is the cleanup barrier — it resolves when `abort()` completes and
|
|
723
|
+
* rejects when the timeout fires. Idempotent: a second call returns the same barrier.
|
|
724
|
+
*/
|
|
725
|
+
#beginCancelCleanup(record: ManagedSessionRecord, promptTurn: PromptTurnState): Promise<void> {
|
|
726
|
+
if (promptTurn.cleanup) {
|
|
727
|
+
return promptTurn.cleanup;
|
|
728
|
+
}
|
|
729
|
+
promptTurn.cancelRequested = true;
|
|
730
|
+
promptTurn.unsubscribe?.();
|
|
731
|
+
const cleanup = this.#runCancelCleanup(record, promptTurn);
|
|
732
|
+
promptTurn.cleanup = cleanup;
|
|
733
|
+
this.#finishPrompt(record, {
|
|
734
|
+
stopReason: "cancelled",
|
|
735
|
+
usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
|
|
736
|
+
userMessageId: promptTurn.userMessageId,
|
|
737
|
+
});
|
|
738
|
+
return cleanup;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async #runCancelCleanup(record: ManagedSessionRecord, promptTurn: PromptTurnState): Promise<void> {
|
|
742
|
+
let timer: NodeJS.Timeout | undefined;
|
|
743
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
744
|
+
timer = setTimeout(() => reject(new Error("ACP cancel cleanup timed out")), this.#cancelCleanupTimeoutMs);
|
|
745
|
+
});
|
|
746
|
+
try {
|
|
747
|
+
await Promise.race([record.session.abort(), timeout]);
|
|
748
|
+
} finally {
|
|
749
|
+
if (timer) clearTimeout(timer);
|
|
750
|
+
// Order matters: clear `cleanup` before evicting the slot so the slot-eviction
|
|
751
|
+
// branch matches what `#finishPrompt` saw if it ran first.
|
|
752
|
+
promptTurn.cleanup = undefined;
|
|
753
|
+
if (promptTurn.settled && record.promptTurn === promptTurn) {
|
|
754
|
+
record.promptTurn = undefined;
|
|
755
|
+
}
|
|
689
756
|
}
|
|
690
757
|
}
|
|
691
758
|
|
|
@@ -929,8 +996,7 @@ export class AcpAgent implements Agent {
|
|
|
929
996
|
async #resolveForkSourceSessionPath(sessionId: string): Promise<string> {
|
|
930
997
|
const loaded = this.#sessions.get(sessionId);
|
|
931
998
|
if (loaded) {
|
|
932
|
-
|
|
933
|
-
if (promptTurn && !promptTurn.settled) {
|
|
999
|
+
if (isPromptTurnInFlight(loaded.promptTurn)) {
|
|
934
1000
|
throw new Error(`ACP session fork is unavailable while a prompt is in progress: ${sessionId}`);
|
|
935
1001
|
}
|
|
936
1002
|
await loaded.session.sessionManager.flush();
|
|
@@ -950,7 +1016,7 @@ export class AcpAgent implements Agent {
|
|
|
950
1016
|
|
|
951
1017
|
async #handlePromptEvent(record: ManagedSessionRecord, event: AgentSessionEvent): Promise<void> {
|
|
952
1018
|
const promptTurn = record.promptTurn;
|
|
953
|
-
if (!promptTurn || promptTurn.settled) {
|
|
1019
|
+
if (!promptTurn || promptTurn.settled || promptTurn.cancelRequested) {
|
|
954
1020
|
return;
|
|
955
1021
|
}
|
|
956
1022
|
|
|
@@ -1019,7 +1085,11 @@ export class AcpAgent implements Agent {
|
|
|
1019
1085
|
}
|
|
1020
1086
|
promptTurn.settled = true;
|
|
1021
1087
|
promptTurn.unsubscribe?.();
|
|
1022
|
-
|
|
1088
|
+
// Keep the slot occupied until cancel cleanup finishes — `#runCancelCleanup`
|
|
1089
|
+
// evicts the slot in its finally block once both flags say it's safe.
|
|
1090
|
+
if (!promptTurn.cleanup && record.promptTurn === promptTurn) {
|
|
1091
|
+
record.promptTurn = undefined;
|
|
1092
|
+
}
|
|
1023
1093
|
if (error !== undefined) {
|
|
1024
1094
|
promptTurn.reject(error);
|
|
1025
1095
|
return;
|
|
@@ -1887,22 +1957,15 @@ export class AcpAgent implements Agent {
|
|
|
1887
1957
|
|
|
1888
1958
|
async #cancelPromptForClose(record: ManagedSessionRecord): Promise<void> {
|
|
1889
1959
|
const promptTurn = record.promptTurn;
|
|
1890
|
-
if (!promptTurn
|
|
1960
|
+
if (!isPromptTurnInFlight(promptTurn)) {
|
|
1891
1961
|
return;
|
|
1892
1962
|
}
|
|
1893
|
-
|
|
1894
|
-
promptTurn.cancelRequested = true;
|
|
1895
|
-
promptTurn.unsubscribe?.();
|
|
1963
|
+
const cleanup = promptTurn.cleanup ?? this.#beginCancelCleanup(record, promptTurn);
|
|
1896
1964
|
try {
|
|
1897
|
-
await
|
|
1965
|
+
await cleanup;
|
|
1898
1966
|
} catch (error) {
|
|
1899
1967
|
logger.warn("Failed to abort ACP prompt during session close", { error });
|
|
1900
1968
|
}
|
|
1901
|
-
this.#finishPrompt(record, {
|
|
1902
|
-
stopReason: "cancelled",
|
|
1903
|
-
usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
|
|
1904
|
-
userMessageId: promptTurn.userMessageId,
|
|
1905
|
-
});
|
|
1906
1969
|
}
|
|
1907
1970
|
|
|
1908
1971
|
async #disposeSessionRecord(record: ManagedSessionRecord): Promise<void> {
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* Component for displaying bash command execution with streaming output.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { sanitizeText } from "@oh-my-pi/pi-natives";
|
|
6
5
|
import {
|
|
7
6
|
Container,
|
|
8
7
|
Ellipsis,
|
|
@@ -14,6 +13,7 @@ import {
|
|
|
14
13
|
truncateToWidth,
|
|
15
14
|
visibleWidth,
|
|
16
15
|
} from "@oh-my-pi/pi-tui";
|
|
16
|
+
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
17
17
|
import { theme } from "../../modes/theme/theme";
|
|
18
18
|
import type { TruncationMeta } from "../../tools/output-meta";
|
|
19
19
|
import { getSixelLineMask, isSixelPassthroughEnabled, sanitizeWithOptionalSixelPassthrough } from "../../utils/sixel";
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { sanitizeText } from "@oh-my-pi/pi-
|
|
2
|
-
import { getIndentation } from "@oh-my-pi/pi-utils";
|
|
1
|
+
import { getIndentation, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
3
2
|
import * as Diff from "diff";
|
|
4
3
|
import { getLanguageFromPath, highlightCode, theme } from "../../modes/theme/theme";
|
|
5
4
|
import { type CodeFrameMarker, formatCodeFrameLine, replaceTabs } from "../../tools/render-utils";
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Shares the same kernel session as the agent's eval tool.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { sanitizeText } from "@oh-my-pi/pi-natives";
|
|
7
6
|
import { Container, type Loader, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
7
|
+
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
8
8
|
import { highlightCode, theme } from "../../modes/theme/theme";
|
|
9
9
|
import type { TruncationMeta } from "../../tools/output-meta";
|
|
10
10
|
import {
|
|
@@ -5,6 +5,8 @@ import { theme } from "../../modes/theme/theme";
|
|
|
5
5
|
import { matchesSelectCancel } from "../../modes/utils/keybinding-matchers";
|
|
6
6
|
import type { AuthStorage } from "../../session/auth-storage";
|
|
7
7
|
import { DynamicBorder } from "./dynamic-border";
|
|
8
|
+
|
|
9
|
+
const OAUTH_SELECTOR_MAX_VISIBLE = 10;
|
|
8
10
|
/**
|
|
9
11
|
* Component that renders an OAuth provider selector.
|
|
10
12
|
*/
|
|
@@ -144,7 +146,16 @@ export class OAuthSelectorComponent extends Container {
|
|
|
144
146
|
}
|
|
145
147
|
#updateList(): void {
|
|
146
148
|
this.#listContainer.clear();
|
|
147
|
-
|
|
149
|
+
|
|
150
|
+
const total = this.#allProviders.length;
|
|
151
|
+
const maxVisible = OAUTH_SELECTOR_MAX_VISIBLE;
|
|
152
|
+
const startIndex =
|
|
153
|
+
total <= maxVisible
|
|
154
|
+
? 0
|
|
155
|
+
: Math.max(0, Math.min(this.#selectedIndex - Math.floor(maxVisible / 2), total - maxVisible));
|
|
156
|
+
const endIndex = Math.min(startIndex + maxVisible, total);
|
|
157
|
+
|
|
158
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
148
159
|
const provider = this.#allProviders[i];
|
|
149
160
|
if (!provider) continue;
|
|
150
161
|
const isSelected = i === this.#selectedIndex;
|
|
@@ -163,8 +174,14 @@ export class OAuthSelectorComponent extends Container {
|
|
|
163
174
|
this.#listContainer.addChild(new TruncatedText(line, 0, 0));
|
|
164
175
|
}
|
|
165
176
|
|
|
177
|
+
// Scroll indicator when list is windowed
|
|
178
|
+
if (startIndex > 0 || endIndex < total) {
|
|
179
|
+
const scrollInfo = theme.fg("muted", ` (${this.#selectedIndex + 1}/${total})`);
|
|
180
|
+
this.#listContainer.addChild(new TruncatedText(scrollInfo, 0, 0));
|
|
181
|
+
}
|
|
182
|
+
|
|
166
183
|
// Show "no providers" if empty
|
|
167
|
-
if (
|
|
184
|
+
if (total === 0) {
|
|
168
185
|
const message =
|
|
169
186
|
this.#mode === "login" ? "No OAuth providers available" : "No OAuth providers logged in. Use /login first.";
|
|
170
187
|
this.#listContainer.addChild(new TruncatedText(theme.fg("muted", ` ${message}`), 0, 0));
|
|
@@ -191,6 +208,25 @@ export class OAuthSelectorComponent extends Container {
|
|
|
191
208
|
this.#statusMessage = undefined;
|
|
192
209
|
this.#updateList();
|
|
193
210
|
}
|
|
211
|
+
// Page up - jump up by one visible page
|
|
212
|
+
else if (matchesKey(keyData, "pageUp")) {
|
|
213
|
+
if (this.#allProviders.length > 0) {
|
|
214
|
+
this.#selectedIndex = Math.max(0, this.#selectedIndex - OAUTH_SELECTOR_MAX_VISIBLE);
|
|
215
|
+
}
|
|
216
|
+
this.#statusMessage = undefined;
|
|
217
|
+
this.#updateList();
|
|
218
|
+
}
|
|
219
|
+
// Page down - jump down by one visible page
|
|
220
|
+
else if (matchesKey(keyData, "pageDown")) {
|
|
221
|
+
if (this.#allProviders.length > 0) {
|
|
222
|
+
this.#selectedIndex = Math.min(
|
|
223
|
+
this.#allProviders.length - 1,
|
|
224
|
+
this.#selectedIndex + OAUTH_SELECTOR_MAX_VISIBLE,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
this.#statusMessage = undefined;
|
|
228
|
+
this.#updateList();
|
|
229
|
+
}
|
|
194
230
|
// Enter
|
|
195
231
|
else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
196
232
|
const selectedProvider = this.#allProviders[this.#selectedIndex];
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import { sanitizeText } from "@oh-my-pi/pi-natives";
|
|
3
2
|
import {
|
|
4
3
|
Box,
|
|
5
4
|
type Component,
|
|
@@ -13,7 +12,7 @@ import {
|
|
|
13
12
|
Text,
|
|
14
13
|
type TUI,
|
|
15
14
|
} from "@oh-my-pi/pi-tui";
|
|
16
|
-
import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
|
15
|
+
import { getProjectDir, logger, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
17
16
|
import { EDIT_MODE_STRATEGIES, type EditMode, type PerFileDiffPreview } from "../../edit";
|
|
18
17
|
import type { Theme } from "../../modes/theme/theme";
|
|
19
18
|
import { theme } from "../../modes/theme/theme";
|