@oh-my-pi/pi-coding-agent 6.8.2 → 6.8.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 +17 -0
- package/package.json +6 -6
- package/src/core/agent-session.ts +1 -5
- package/src/core/custom-commands/loader.ts +0 -8
- package/src/core/keybindings.ts +10 -2
- package/src/core/tools/output.ts +7 -8
- package/src/core/tools/task/executor.ts +7 -16
- package/src/core/tools/task/index.ts +11 -12
- package/src/core/tools/task/types.ts +2 -1
- package/src/core/tools/todo-write.ts +2 -18
- package/src/modes/interactive/components/todo-display.ts +1 -8
- package/src/modes/interactive/components/tree-selector.ts +2 -2
- package/src/modes/interactive/interactive-mode.ts +1 -15
- package/src/utils/clipboard.ts +27 -20
- package/src/core/tools/task/artifacts.ts +0 -112
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [6.8.3] - 2026-01-21
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Updated keybinding system to normalize key IDs to lowercase
|
|
10
|
+
- Changed label edit shortcut from 'l' to 'Shift+L' in tree selector
|
|
11
|
+
- Changed output file extension from `.out.md` to `.md` for artifacts
|
|
12
|
+
|
|
13
|
+
### Removed
|
|
14
|
+
|
|
15
|
+
- Removed bundled worktree command from custom commands loader
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Fixed keybinding case sensitivity issues by normalizing all key IDs
|
|
20
|
+
- Fixed task artifact path handling and simplified file structure
|
|
21
|
+
|
|
5
22
|
## [6.8.2] - 2026-01-21
|
|
6
23
|
|
|
7
24
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "6.8.
|
|
3
|
+
"version": "6.8.3",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
"prepublishOnly": "bun run generate-template && bun run clean && bun run build"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@oh-my-pi/pi-agent-core": "6.8.
|
|
44
|
-
"@oh-my-pi/pi-ai": "6.8.
|
|
45
|
-
"@oh-my-pi/pi-git-tool": "6.8.
|
|
46
|
-
"@oh-my-pi/pi-tui": "6.8.
|
|
47
|
-
"@oh-my-pi/pi-utils": "6.8.
|
|
43
|
+
"@oh-my-pi/pi-agent-core": "6.8.3",
|
|
44
|
+
"@oh-my-pi/pi-ai": "6.8.3",
|
|
45
|
+
"@oh-my-pi/pi-git-tool": "6.8.3",
|
|
46
|
+
"@oh-my-pi/pi-tui": "6.8.3",
|
|
47
|
+
"@oh-my-pi/pi-utils": "6.8.3",
|
|
48
48
|
"@openai/agents": "^0.3.7",
|
|
49
49
|
"@sinclair/typebox": "^0.34.46",
|
|
50
50
|
"ajv": "^8.17.1",
|
|
@@ -63,7 +63,6 @@ import { unmountAll } from "./ssh/sshfs-mount";
|
|
|
63
63
|
import type { BashOperations } from "./tools/bash";
|
|
64
64
|
import { normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "./tools/patch";
|
|
65
65
|
import { resolveToCwd } from "./tools/path-utils";
|
|
66
|
-
import { getArtifactsDir } from "./tools/task/artifacts";
|
|
67
66
|
import type { TodoItem } from "./tools/todo-write";
|
|
68
67
|
import type { TtsrManager } from "./ttsr";
|
|
69
68
|
|
|
@@ -1991,10 +1990,7 @@ export class AgentSession {
|
|
|
1991
1990
|
const sessionFile = this.sessionManager.getSessionFile();
|
|
1992
1991
|
if (!sessionFile) return;
|
|
1993
1992
|
|
|
1994
|
-
const
|
|
1995
|
-
if (!artifactsDir) return;
|
|
1996
|
-
|
|
1997
|
-
const todoPath = `${artifactsDir}/todos.json`;
|
|
1993
|
+
const todoPath = `${sessionFile.slice(0, -6)}/todos.json`;
|
|
1998
1994
|
const file = Bun.file(todoPath);
|
|
1999
1995
|
if (!(await file.exists())) {
|
|
2000
1996
|
this._todoReminderCount = 0;
|
|
@@ -13,7 +13,6 @@ import { getAgentDir, getConfigDirs } from "../../config";
|
|
|
13
13
|
import * as piCodingAgent from "../../index";
|
|
14
14
|
import { execCommand } from "../exec";
|
|
15
15
|
import { ReviewCommand } from "./bundled/review";
|
|
16
|
-
import { WorktreeCommand } from "./bundled/wt";
|
|
17
16
|
import type {
|
|
18
17
|
CustomCommand,
|
|
19
18
|
CustomCommandAPI,
|
|
@@ -153,13 +152,6 @@ function loadBundledCommands(sharedApi: CustomCommandAPI): LoadedCustomCommand[]
|
|
|
153
152
|
source: "bundled",
|
|
154
153
|
});
|
|
155
154
|
|
|
156
|
-
bundled.push({
|
|
157
|
-
path: "bundled:wt",
|
|
158
|
-
resolvedPath: "bundled:wt",
|
|
159
|
-
command: new WorktreeCommand(sharedApi),
|
|
160
|
-
source: "bundled",
|
|
161
|
-
});
|
|
162
|
-
|
|
163
155
|
return bundled;
|
|
164
156
|
}
|
|
165
157
|
|
package/src/core/keybindings.ts
CHANGED
|
@@ -124,6 +124,8 @@ const KEY_LABELS: Record<string, string> = {
|
|
|
124
124
|
right: "Right",
|
|
125
125
|
};
|
|
126
126
|
|
|
127
|
+
const normalizeKeyId = (key: KeyId): KeyId => key.toLowerCase() as KeyId;
|
|
128
|
+
|
|
127
129
|
function formatKeyPart(part: string): string {
|
|
128
130
|
const lower = part.toLowerCase();
|
|
129
131
|
const modifier = MODIFIER_LABELS[lower];
|
|
@@ -199,14 +201,20 @@ export class KeybindingsManager {
|
|
|
199
201
|
// Set defaults for app actions
|
|
200
202
|
for (const [action, keys] of Object.entries(DEFAULT_APP_KEYBINDINGS)) {
|
|
201
203
|
const keyArray = Array.isArray(keys) ? keys : [keys];
|
|
202
|
-
this.appActionToKeys.set(
|
|
204
|
+
this.appActionToKeys.set(
|
|
205
|
+
action as AppAction,
|
|
206
|
+
keyArray.map((key) => normalizeKeyId(key as KeyId)),
|
|
207
|
+
);
|
|
203
208
|
}
|
|
204
209
|
|
|
205
210
|
// Override with user config (app actions only)
|
|
206
211
|
for (const [action, keys] of Object.entries(this.config)) {
|
|
207
212
|
if (keys === undefined || !isAppAction(action)) continue;
|
|
208
213
|
const keyArray = Array.isArray(keys) ? keys : [keys];
|
|
209
|
-
this.appActionToKeys.set(
|
|
214
|
+
this.appActionToKeys.set(
|
|
215
|
+
action,
|
|
216
|
+
keyArray.map((key) => normalizeKeyId(key as KeyId)),
|
|
217
|
+
);
|
|
210
218
|
}
|
|
211
219
|
}
|
|
212
220
|
|
package/src/core/tools/output.ts
CHANGED
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
TRUNCATE_LENGTHS,
|
|
26
26
|
truncate,
|
|
27
27
|
} from "./render-utils";
|
|
28
|
-
import { getArtifactsDir } from "./task/artifacts";
|
|
29
28
|
|
|
30
29
|
const outputSchema = Type.Object({
|
|
31
30
|
ids: Type.Array(Type.String(), {
|
|
@@ -164,7 +163,7 @@ function applyQuery(data: unknown, query: string): unknown {
|
|
|
164
163
|
function listAvailableOutputs(artifactsDir: string): string[] {
|
|
165
164
|
try {
|
|
166
165
|
const files = fs.readdirSync(artifactsDir);
|
|
167
|
-
return files.filter((f) => f.endsWith(".
|
|
166
|
+
return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
|
|
168
167
|
} catch {
|
|
169
168
|
return [];
|
|
170
169
|
}
|
|
@@ -274,8 +273,8 @@ export class OutputTool implements AgentTool<typeof outputSchema, OutputToolDeta
|
|
|
274
273
|
};
|
|
275
274
|
}
|
|
276
275
|
|
|
277
|
-
const artifactsDir =
|
|
278
|
-
if (!
|
|
276
|
+
const artifactsDir = sessionFile.slice(0, -6); // strip .jsonl extension
|
|
277
|
+
if (!fs.existsSync(artifactsDir)) {
|
|
279
278
|
return {
|
|
280
279
|
content: [{ type: "text", text: "No artifacts directory found" }],
|
|
281
280
|
details: { outputs: [], notFound: params.ids },
|
|
@@ -296,14 +295,14 @@ export class OutputTool implements AgentTool<typeof outputSchema, OutputToolDeta
|
|
|
296
295
|
const queryResults: Array<{ id: string; value: unknown }> = [];
|
|
297
296
|
|
|
298
297
|
for (const id of params.ids) {
|
|
299
|
-
const outputPath = path.join(artifactsDir, `${id}.
|
|
300
|
-
|
|
301
|
-
if (!
|
|
298
|
+
const outputPath = path.join(artifactsDir, `${id}.md`);
|
|
299
|
+
const file = Bun.file(outputPath);
|
|
300
|
+
if (!(await file.exists())) {
|
|
302
301
|
notFound.push(id);
|
|
303
302
|
continue;
|
|
304
303
|
}
|
|
305
304
|
|
|
306
|
-
const rawContent =
|
|
305
|
+
const rawContent = await file.text();
|
|
307
306
|
const rawLines = rawContent.split("\n");
|
|
308
307
|
const totalLines = rawLines.length;
|
|
309
308
|
const totalChars = rawContent.length;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Runs each subagent in a Bun Worker and forwards AgentEvents for progress tracking.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import path from "node:path";
|
|
7
8
|
import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
8
9
|
import type { AuthStorage } from "../../auth-storage";
|
|
9
10
|
import type { EventBus } from "../../event-bus";
|
|
@@ -16,7 +17,6 @@ import type { ToolSession } from "..";
|
|
|
16
17
|
import { LspTool } from "../lsp/index";
|
|
17
18
|
import type { LspParams } from "../lsp/types";
|
|
18
19
|
import { PythonTool } from "../python";
|
|
19
|
-
import { ensureArtifactsDir, getArtifactPaths } from "./artifacts";
|
|
20
20
|
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
21
21
|
import {
|
|
22
22
|
type AgentDefinition,
|
|
@@ -256,20 +256,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
256
256
|
const fullTask = context ? `${context}\n\n${task}` : task;
|
|
257
257
|
|
|
258
258
|
// Set up artifact paths and write input file upfront if artifacts dir provided
|
|
259
|
-
let artifactPaths: { inputPath: string; outputPath: string; jsonlPath: string } | undefined;
|
|
260
259
|
let subtaskSessionFile: string | undefined;
|
|
261
|
-
|
|
262
260
|
if (options.artifactsDir) {
|
|
263
|
-
|
|
264
|
-
artifactPaths = getArtifactPaths(options.artifactsDir, taskId);
|
|
265
|
-
subtaskSessionFile = artifactPaths.jsonlPath;
|
|
266
|
-
|
|
267
|
-
// Write input file immediately (real-time visibility)
|
|
268
|
-
try {
|
|
269
|
-
await Bun.write(artifactPaths.inputPath, fullTask);
|
|
270
|
-
} catch {
|
|
271
|
-
// Non-fatal, continue without input artifact
|
|
272
|
-
}
|
|
261
|
+
subtaskSessionFile = path.join(options.artifactsDir, `${taskId}.jsonl`);
|
|
273
262
|
}
|
|
274
263
|
|
|
275
264
|
// Add tools if specified
|
|
@@ -1042,9 +1031,11 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1042
1031
|
// Write output artifact (input and jsonl already written in real-time)
|
|
1043
1032
|
// Compute output metadata for Output tool integration
|
|
1044
1033
|
let outputMeta: { lineCount: number; charCount: number } | undefined;
|
|
1045
|
-
|
|
1034
|
+
let outputPath: string | undefined;
|
|
1035
|
+
if (options.artifactsDir) {
|
|
1036
|
+
outputPath = path.join(options.artifactsDir, `${taskId}.md`);
|
|
1046
1037
|
try {
|
|
1047
|
-
await Bun.write(
|
|
1038
|
+
await Bun.write(outputPath, rawOutput);
|
|
1048
1039
|
outputMeta = {
|
|
1049
1040
|
lineCount: rawOutput.split("\n").length,
|
|
1050
1041
|
charCount: rawOutput.length,
|
|
@@ -1076,7 +1067,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1076
1067
|
error: exitCode !== 0 && stderr ? stderr : undefined,
|
|
1077
1068
|
aborted: wasAborted,
|
|
1078
1069
|
usage: hasUsage ? accumulatedUsage : undefined,
|
|
1079
|
-
|
|
1070
|
+
outputPath,
|
|
1080
1071
|
extractedToolData: progress.extractedToolData,
|
|
1081
1072
|
outputMeta,
|
|
1082
1073
|
};
|
|
@@ -13,13 +13,17 @@
|
|
|
13
13
|
* - Session artifacts for debugging
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import path from "node:path";
|
|
16
19
|
import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
17
20
|
import type { Usage } from "@oh-my-pi/pi-ai";
|
|
21
|
+
import { nanoid } from "nanoid";
|
|
18
22
|
import type { Theme } from "../../../modes/interactive/theme/theme";
|
|
19
23
|
import taskDescriptionTemplate from "../../../prompts/tools/task.md" with { type: "text" };
|
|
20
24
|
import { renderPromptTemplate } from "../../prompt-templates";
|
|
25
|
+
import type { ToolSession } from "..";
|
|
21
26
|
import { formatDuration } from "../render-utils";
|
|
22
|
-
import { cleanupTempDir, createTempArtifactsDir, getArtifactsDir } from "./artifacts";
|
|
23
27
|
import { discoverAgents, getAgent } from "./discovery";
|
|
24
28
|
import { runSubprocess } from "./executor";
|
|
25
29
|
import { mapWithConcurrencyLimit } from "./parallel";
|
|
@@ -36,7 +40,6 @@ import {
|
|
|
36
40
|
|
|
37
41
|
// Import review tools for side effects (registers subagent tool handlers)
|
|
38
42
|
import "../review";
|
|
39
|
-
import type { ToolSession } from "..";
|
|
40
43
|
|
|
41
44
|
/** Format byte count for display */
|
|
42
45
|
function formatBytes(bytes: number): string {
|
|
@@ -276,9 +279,10 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
276
279
|
|
|
277
280
|
// Derive artifacts directory
|
|
278
281
|
const sessionFile = this.session.getSessionFile();
|
|
279
|
-
const artifactsDir = sessionFile ?
|
|
280
|
-
const tempArtifactsDir = artifactsDir ? null :
|
|
282
|
+
const artifactsDir = sessionFile ? sessionFile.slice(0, -6) : null;
|
|
283
|
+
const tempArtifactsDir = artifactsDir ? null : path.join(tmpdir(), `omp-task-${nanoid()}`);
|
|
281
284
|
const effectiveArtifactsDir = artifactsDir || tempArtifactsDir!;
|
|
285
|
+
await mkdir(effectiveArtifactsDir, { recursive: true });
|
|
282
286
|
|
|
283
287
|
// Initialize progress tracking
|
|
284
288
|
const progressMap = new Map<number, AgentProgress>();
|
|
@@ -435,8 +439,8 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
435
439
|
// Collect output paths (artifacts already written by executor in real-time)
|
|
436
440
|
const outputPaths: string[] = [];
|
|
437
441
|
for (const result of results) {
|
|
438
|
-
if (result.
|
|
439
|
-
outputPaths.push(result.
|
|
442
|
+
if (result.outputPath) {
|
|
443
|
+
outputPaths.push(result.outputPath);
|
|
440
444
|
}
|
|
441
445
|
}
|
|
442
446
|
|
|
@@ -468,7 +472,7 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
468
472
|
|
|
469
473
|
// Cleanup temp directory if used
|
|
470
474
|
if (tempArtifactsDir) {
|
|
471
|
-
await
|
|
475
|
+
await rm(tempArtifactsDir, { recursive: true, force: true });
|
|
472
476
|
}
|
|
473
477
|
|
|
474
478
|
return {
|
|
@@ -482,11 +486,6 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
482
486
|
},
|
|
483
487
|
};
|
|
484
488
|
} catch (err) {
|
|
485
|
-
// Cleanup temp directory on error
|
|
486
|
-
if (tempArtifactsDir) {
|
|
487
|
-
await cleanupTempDir(tempArtifactsDir);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
489
|
return {
|
|
491
490
|
content: [{ type: "text", text: `Task execution failed: ${err}` }],
|
|
492
491
|
details: {
|
|
@@ -153,7 +153,8 @@ export interface SingleResult {
|
|
|
153
153
|
aborted?: boolean;
|
|
154
154
|
/** Aggregated usage from the subprocess, accumulated incrementally from message_end events. */
|
|
155
155
|
usage?: Usage;
|
|
156
|
-
|
|
156
|
+
/** Output path for the task result */
|
|
157
|
+
outputPath?: string;
|
|
157
158
|
/** Data extracted by registered subprocess tool handlers (keyed by tool name) */
|
|
158
159
|
extractedToolData?: Record<string, unknown[]>;
|
|
159
160
|
/** Output metadata for Output tool integration */
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { mkdirSync } from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
5
4
|
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
@@ -13,7 +12,6 @@ import todoWriteDescription from "../../prompts/tools/todo-write.md" with { type
|
|
|
13
12
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
14
13
|
import { renderPromptTemplate } from "../prompt-templates";
|
|
15
14
|
import type { ToolSession } from "../sdk";
|
|
16
|
-
import { ensureArtifactsDir, getArtifactsDir } from "./task/artifacts";
|
|
17
15
|
|
|
18
16
|
const todoWriteSchema = Type.Object({
|
|
19
17
|
todos: Type.Array(
|
|
@@ -135,10 +133,6 @@ async function loadTodoFile(filePath: string): Promise<TodoFile | null> {
|
|
|
135
133
|
}
|
|
136
134
|
}
|
|
137
135
|
|
|
138
|
-
async function saveTodoFile(filePath: string, data: TodoFile): Promise<void> {
|
|
139
|
-
await Bun.write(filePath, JSON.stringify(data, null, 2));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
136
|
function formatTodoSummary(todos: TodoItem[]): string {
|
|
143
137
|
if (todos.length === 0) return "Todo list cleared.";
|
|
144
138
|
const completed = todos.filter((t) => t.status === "completed").length;
|
|
@@ -200,24 +194,14 @@ export class TodoWriteTool implements AgentTool<typeof todoWriteSchema, TodoWrit
|
|
|
200
194
|
};
|
|
201
195
|
}
|
|
202
196
|
|
|
203
|
-
const
|
|
204
|
-
if (!artifactsDir) {
|
|
205
|
-
return {
|
|
206
|
-
content: [{ type: "text", text: formatTodoSummary(todos) }],
|
|
207
|
-
details: { todos, updatedAt, storage: "memory" },
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
ensureArtifactsDir(artifactsDir);
|
|
212
|
-
const todoPath = path.join(artifactsDir, TODO_FILE_NAME);
|
|
197
|
+
const todoPath = path.join(sessionFile.slice(0, -6), TODO_FILE_NAME);
|
|
213
198
|
const existing = await loadTodoFile(todoPath);
|
|
214
199
|
const storedTodos = existing?.todos ?? [];
|
|
215
200
|
const merged = todos.length > 0 ? todos : [];
|
|
216
201
|
const fileData: TodoFile = { updatedAt, todos: merged };
|
|
217
202
|
|
|
218
203
|
try {
|
|
219
|
-
|
|
220
|
-
await saveTodoFile(todoPath, fileData);
|
|
204
|
+
await Bun.write(todoPath, JSON.stringify(fileData, null, 2));
|
|
221
205
|
} catch (error) {
|
|
222
206
|
logger.error("Failed to write todo file", { path: todoPath, error: String(error) });
|
|
223
207
|
return {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
4
|
-
import { getArtifactsDir } from "../../../core/tools/task/artifacts";
|
|
5
4
|
import { theme } from "../theme/theme";
|
|
6
5
|
import type { TodoItem } from "../types";
|
|
7
6
|
|
|
@@ -40,13 +39,7 @@ export class TodoDisplayComponent {
|
|
|
40
39
|
return;
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
const artifactsDir =
|
|
44
|
-
if (!artifactsDir) {
|
|
45
|
-
this.todos = [];
|
|
46
|
-
this.visible = false;
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
42
|
+
const artifactsDir = this.sessionFile.slice(0, -6); // strip .jsonl extension
|
|
50
43
|
const todoPath = path.join(artifactsDir, TODO_FILE_NAME);
|
|
51
44
|
const data = await loadTodoFile(todoPath);
|
|
52
45
|
this.todos = data?.todos ?? [];
|
|
@@ -707,7 +707,7 @@ class TreeList implements Component {
|
|
|
707
707
|
this.searchQuery = this.searchQuery.slice(0, -1);
|
|
708
708
|
this.applyFilter();
|
|
709
709
|
}
|
|
710
|
-
} else if (keyData
|
|
710
|
+
} else if (matchesKey(keyData, "shift+l") && !this.searchQuery) {
|
|
711
711
|
const selected = this.filteredNodes[this.selectedIndex];
|
|
712
712
|
if (selected && this.onLabelEdit) {
|
|
713
713
|
this.onLabelEdit(selected.node.entry.id, selected.node.label);
|
|
@@ -821,7 +821,7 @@ export class TreeSelectorComponent extends Container {
|
|
|
821
821
|
new TruncatedText(
|
|
822
822
|
theme.fg(
|
|
823
823
|
"muted",
|
|
824
|
-
" Up/Down: move. Left/Right: page.
|
|
824
|
+
" Up/Down: move. Left/Right: page. Shift+L: label. Ctrl+O/Shift+Ctrl+O: filter. Alt+D/T/U/L/A: filter. Type to search",
|
|
825
825
|
),
|
|
826
826
|
0,
|
|
827
827
|
0,
|
|
@@ -28,7 +28,6 @@ import { getRecentSessions } from "../../core/session-manager";
|
|
|
28
28
|
import type { SettingsManager } from "../../core/settings-manager";
|
|
29
29
|
import { loadSlashCommands } from "../../core/slash-commands";
|
|
30
30
|
import { setTerminalTitle } from "../../core/title-generator";
|
|
31
|
-
import { getArtifactsDir } from "../../core/tools/task/artifacts";
|
|
32
31
|
import { VoiceSupervisor } from "../../core/voice-supervisor";
|
|
33
32
|
import type { AssistantMessageComponent } from "./components/assistant-message";
|
|
34
33
|
import type { BashExecutionComponent } from "./components/bash-execution";
|
|
@@ -384,15 +383,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
384
383
|
|
|
385
384
|
// Initial top border update
|
|
386
385
|
this.updateEditorTopBorder();
|
|
387
|
-
|
|
388
|
-
if (!startupQuiet) {
|
|
389
|
-
const templateNames = this.session.promptTemplates.map((template) => template.name).sort();
|
|
390
|
-
if (templateNames.length > 0) {
|
|
391
|
-
const preview = templateNames.slice(0, 3).join(", ");
|
|
392
|
-
const suffix = templateNames.length > 3 ? ` +${templateNames.length - 3} more` : "";
|
|
393
|
-
this.showStatus(`Loaded prompt templates: ${preview}${suffix}`);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
386
|
}
|
|
397
387
|
|
|
398
388
|
async getUserInput(): Promise<{ text: string; images?: ImageContent[] }> {
|
|
@@ -481,11 +471,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
481
471
|
this.renderTodoList();
|
|
482
472
|
return;
|
|
483
473
|
}
|
|
484
|
-
const artifactsDir =
|
|
485
|
-
if (!artifactsDir) {
|
|
486
|
-
this.renderTodoList();
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
474
|
+
const artifactsDir = sessionFile.slice(0, -6);
|
|
489
475
|
const todoPath = path.join(artifactsDir, TODO_FILE_NAME);
|
|
490
476
|
const file = Bun.file(todoPath);
|
|
491
477
|
if (!(await file.exists())) {
|
package/src/utils/clipboard.ts
CHANGED
|
@@ -32,33 +32,40 @@ function selectPreferredImageMimeType(mimeTypes: string[]): string | null {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export async function copyToClipboard(text: string): Promise<void> {
|
|
35
|
-
|
|
35
|
+
const p = platform();
|
|
36
|
+
const timeout = 5000;
|
|
37
|
+
|
|
36
38
|
try {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
|
|
39
|
+
if (p === "darwin") {
|
|
40
|
+
await Bun.spawn(["pbcopy"], { stdin: Buffer.from(text), timeout }).exited;
|
|
41
|
+
} else if (p === "win32") {
|
|
42
|
+
await Bun.spawn(["clip"], { stdin: Buffer.from(text), timeout }).exited;
|
|
43
|
+
} else {
|
|
44
|
+
const wayland = isWaylandSession();
|
|
45
|
+
if (wayland) {
|
|
46
|
+
const wlCopyPath = Bun.which("wl-copy");
|
|
47
|
+
if (wlCopyPath) {
|
|
48
|
+
// Fire-and-forget: wl-copy may not exit promptly, so we unref to avoid blocking
|
|
49
|
+
void Bun.spawn([wlCopyPath], { stdin: Buffer.from(text), timeout }).unref();
|
|
47
50
|
return;
|
|
48
|
-
} else {
|
|
49
|
-
promise = $`xclip -selection clipboard -t text/plain -i ${text}`.quiet().then(() => void 0);
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Linux - try xclip first, fall back to xsel
|
|
55
|
+
try {
|
|
56
|
+
await Bun.spawn(["xclip", "-selection", "clipboard"], { stdin: Buffer.from(text), timeout }).exited;
|
|
57
|
+
} catch {
|
|
58
|
+
await Bun.spawn(["xsel", "--clipboard", "--input"], { stdin: Buffer.from(text), timeout }).exited;
|
|
59
|
+
}
|
|
54
60
|
}
|
|
55
61
|
} catch (error) {
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
63
|
+
if (p === "linux") {
|
|
64
|
+
const tools = isWaylandSession() ? "wl-copy, xclip, or xsel" : "xclip or xsel";
|
|
65
|
+
throw new Error(`Failed to copy to clipboard. Install ${tools}: ${msg}`);
|
|
58
66
|
}
|
|
59
|
-
throw new Error(`Failed to copy to clipboard: ${
|
|
67
|
+
throw new Error(`Failed to copy to clipboard: ${msg}`);
|
|
60
68
|
}
|
|
61
|
-
await Promise.race([promise, Bun.sleep(3000)]);
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
export interface ClipboardImage {
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session artifacts for subagent outputs.
|
|
3
|
-
*
|
|
4
|
-
* When a session exists, writes agent outputs to a sibling directory.
|
|
5
|
-
* Otherwise uses temp files that are cleaned up after execution.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import * as fs from "node:fs";
|
|
9
|
-
import * as os from "node:os";
|
|
10
|
-
import * as path from "node:path";
|
|
11
|
-
import { nanoid } from "nanoid";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Derive artifacts directory from session file path.
|
|
15
|
-
*
|
|
16
|
-
* /path/to/sessions/project/2026-01-01T14-28-11-636Z_uuid.jsonl
|
|
17
|
-
* → /path/to/sessions/project/2026-01-01T14-28-11-636Z_uuid/
|
|
18
|
-
*/
|
|
19
|
-
export function getArtifactsDir(sessionFile: string | null): string | null {
|
|
20
|
-
if (!sessionFile) return null;
|
|
21
|
-
// Strip .jsonl extension to get directory path
|
|
22
|
-
if (sessionFile.endsWith(".jsonl")) {
|
|
23
|
-
return sessionFile.slice(0, -6);
|
|
24
|
-
}
|
|
25
|
-
return sessionFile;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Ensure artifacts directory exists.
|
|
30
|
-
*/
|
|
31
|
-
export function ensureArtifactsDir(dir: string): void {
|
|
32
|
-
if (!fs.existsSync(dir)) {
|
|
33
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Generate artifact file paths for an agent run.
|
|
39
|
-
*/
|
|
40
|
-
export function getArtifactPaths(
|
|
41
|
-
dir: string,
|
|
42
|
-
taskId: string,
|
|
43
|
-
): { inputPath: string; outputPath: string; jsonlPath: string } {
|
|
44
|
-
return {
|
|
45
|
-
inputPath: path.join(dir, `${taskId}.in.md`),
|
|
46
|
-
outputPath: path.join(dir, `${taskId}.out.md`),
|
|
47
|
-
jsonlPath: path.join(dir, `${taskId}.jsonl`),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Write artifacts for an agent run.
|
|
53
|
-
*/
|
|
54
|
-
export async function writeArtifacts(
|
|
55
|
-
dir: string,
|
|
56
|
-
taskId: string,
|
|
57
|
-
input: string,
|
|
58
|
-
output: string,
|
|
59
|
-
jsonlEvents?: string[],
|
|
60
|
-
): Promise<{ inputPath: string; outputPath: string; jsonlPath?: string }> {
|
|
61
|
-
ensureArtifactsDir(dir);
|
|
62
|
-
|
|
63
|
-
const paths = getArtifactPaths(dir, taskId);
|
|
64
|
-
|
|
65
|
-
// Write input
|
|
66
|
-
await Bun.write(paths.inputPath, input);
|
|
67
|
-
|
|
68
|
-
// Write output
|
|
69
|
-
await Bun.write(paths.outputPath, output);
|
|
70
|
-
|
|
71
|
-
// Write JSONL if events provided
|
|
72
|
-
if (jsonlEvents && jsonlEvents.length > 0) {
|
|
73
|
-
await Bun.write(paths.jsonlPath, jsonlEvents.join("\n"));
|
|
74
|
-
return paths;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return { inputPath: paths.inputPath, outputPath: paths.outputPath };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Create a temporary artifacts directory.
|
|
82
|
-
*/
|
|
83
|
-
export function createTempArtifactsDir(runId?: string): string {
|
|
84
|
-
const id = runId || nanoid();
|
|
85
|
-
const dir = path.join(os.tmpdir(), `omp-task-${id}`);
|
|
86
|
-
ensureArtifactsDir(dir);
|
|
87
|
-
return dir;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Clean up temporary artifacts.
|
|
92
|
-
*/
|
|
93
|
-
export async function cleanupTempArtifacts(paths: string[]): Promise<void> {
|
|
94
|
-
for (const p of paths) {
|
|
95
|
-
try {
|
|
96
|
-
await fs.promises.unlink(p);
|
|
97
|
-
} catch {
|
|
98
|
-
// Ignore cleanup errors
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Clean up a temporary directory and its contents.
|
|
105
|
-
*/
|
|
106
|
-
export async function cleanupTempDir(dir: string): Promise<void> {
|
|
107
|
-
try {
|
|
108
|
-
await fs.promises.rm(dir, { recursive: true, force: true });
|
|
109
|
-
} catch {
|
|
110
|
-
// Ignore cleanup errors
|
|
111
|
-
}
|
|
112
|
-
}
|