@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 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.2",
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.2",
44
- "@oh-my-pi/pi-ai": "6.8.2",
45
- "@oh-my-pi/pi-git-tool": "6.8.2",
46
- "@oh-my-pi/pi-tui": "6.8.2",
47
- "@oh-my-pi/pi-utils": "6.8.2",
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 artifactsDir = getArtifactsDir(sessionFile);
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
 
@@ -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(action as AppAction, [...keyArray]);
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(action, keyArray);
214
+ this.appActionToKeys.set(
215
+ action,
216
+ keyArray.map((key) => normalizeKeyId(key as KeyId)),
217
+ );
210
218
  }
211
219
  }
212
220
 
@@ -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(".out.md")).map((f) => f.replace(".out.md", ""));
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 = getArtifactsDir(sessionFile);
278
- if (!artifactsDir || !fs.existsSync(artifactsDir)) {
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}.out.md`);
300
-
301
- if (!fs.existsSync(outputPath)) {
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 = fs.readFileSync(outputPath, "utf-8");
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
- ensureArtifactsDir(options.artifactsDir);
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
- if (artifactPaths) {
1034
+ let outputPath: string | undefined;
1035
+ if (options.artifactsDir) {
1036
+ outputPath = path.join(options.artifactsDir, `${taskId}.md`);
1046
1037
  try {
1047
- await Bun.write(artifactPaths.outputPath, rawOutput);
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
- artifactPaths,
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 ? getArtifactsDir(sessionFile) : null;
280
- const tempArtifactsDir = artifactsDir ? null : createTempArtifactsDir();
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.artifactPaths) {
439
- outputPaths.push(result.artifactPaths.outputPath);
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 cleanupTempDir(tempArtifactsDir);
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
- artifactPaths?: { inputPath: string; outputPath: string; jsonlPath?: string };
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 artifactsDir = getArtifactsDir(sessionFile);
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
- mkdirSync(artifactsDir, { recursive: true });
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 = getArtifactsDir(this.sessionFile);
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 === "l" && !this.searchQuery) {
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. l: label. Ctrl+O/Shift+Ctrl+O: filter. Alt+D/T/U/L/A: filter. Type to search",
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 = getArtifactsDir(sessionFile);
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())) {
@@ -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
- let promise: Promise<void>;
35
+ const p = platform();
36
+ const timeout = 5000;
37
+
36
38
  try {
37
- switch (platform()) {
38
- case "darwin":
39
- promise = $`pbcopy ${text}`.quiet().then(() => void 0);
40
- break;
41
- case "win32":
42
- promise = $`clip ${text}`.quiet().then(() => void 0);
43
- break;
44
- case "linux":
45
- if (isWaylandSession()) {
46
- $`wl-copy ${text}`.quiet(); // fire and forget
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
- break;
52
- default:
53
- throw new Error(`Unsupported platform: ${platform()}`);
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
- if (error instanceof Error) {
57
- throw new Error(`Failed to copy to clipboard: ${error.message}`, { cause: error });
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: ${String(error)}`, { cause: error });
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
- }