@oh-my-pi/pi-coding-agent 6.8.1 → 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 +34 -0
- package/package.json +6 -6
- package/src/core/agent-session.ts +14 -9
- package/src/core/auth-storage.ts +5 -1
- package/src/core/bash-executor.ts +3 -2
- package/src/core/custom-commands/loader.ts +0 -8
- package/src/core/keybindings.ts +10 -2
- package/src/core/prompt-templates.ts +1 -1
- package/src/core/tools/lsp/client.ts +1 -1
- package/src/core/tools/output.ts +7 -8
- package/src/core/tools/patch/applicator.ts +38 -24
- package/src/core/tools/patch/diff.ts +7 -3
- package/src/core/tools/patch/fuzzy.ts +19 -1
- package/src/core/tools/patch/index.ts +4 -1
- package/src/core/tools/patch/types.ts +4 -0
- package/src/core/tools/task/executor.ts +28 -21
- package/src/core/tools/task/index.ts +11 -12
- package/src/core/tools/task/types.ts +2 -1
- package/src/core/tools/task/worker.ts +2 -1
- package/src/core/tools/todo-write.ts +2 -18
- package/src/core/tools/web-scrapers/youtube.ts +6 -49
- package/src/lib/worktree/collapse.ts +3 -3
- package/src/lib/worktree/git.ts +6 -40
- package/src/lib/worktree/index.ts +1 -1
- 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 +26 -21
- package/src/core/tools/task/artifacts.ts +0 -112
- package/src/core/tools/task/model-resolver.ts +0 -206
|
@@ -799,10 +799,11 @@ const reportFatal = async (message: string): Promise<void> => {
|
|
|
799
799
|
} catch {
|
|
800
800
|
// Ignore cleanup errors
|
|
801
801
|
}
|
|
802
|
+
const error = new Error(message);
|
|
802
803
|
|
|
803
804
|
const runState = activeRun;
|
|
804
805
|
if (runState) {
|
|
805
|
-
runState.abortController.abort();
|
|
806
|
+
runState.abortController.abort(error);
|
|
806
807
|
if (runState.session) {
|
|
807
808
|
void runState.session.abort();
|
|
808
809
|
}
|
|
@@ -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 {
|
|
@@ -2,7 +2,6 @@ import { unlinkSync } from "node:fs";
|
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { cspawn } from "@oh-my-pi/pi-utils";
|
|
5
|
-
import type { FileSink } from "bun";
|
|
6
5
|
import { nanoid } from "nanoid";
|
|
7
6
|
import { ensureTool } from "../../../utils/tools-manager";
|
|
8
7
|
import type { RenderResult, SpecialHandler } from "./types";
|
|
@@ -16,59 +15,17 @@ async function exec(
|
|
|
16
15
|
args: string[],
|
|
17
16
|
options?: { timeout?: number; input?: string | Buffer; signal?: AbortSignal },
|
|
18
17
|
): Promise<{ stdout: string; stderr: string; ok: boolean; exitCode: number | null }> {
|
|
19
|
-
const controller = new AbortController();
|
|
20
|
-
const onAbort = () => controller.abort(options?.signal?.reason ?? new Error("Aborted"));
|
|
21
|
-
if (options?.signal) {
|
|
22
|
-
if (options.signal.aborted) {
|
|
23
|
-
onAbort();
|
|
24
|
-
} else {
|
|
25
|
-
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
const timeoutId =
|
|
29
|
-
options?.timeout && options.timeout > 0
|
|
30
|
-
? setTimeout(() => controller.abort(new Error("Timeout")), options.timeout)
|
|
31
|
-
: undefined;
|
|
32
18
|
const proc = cspawn([cmd, ...args], {
|
|
33
|
-
signal:
|
|
19
|
+
signal: options?.signal,
|
|
20
|
+
timeout: options?.timeout,
|
|
21
|
+
stdin: options?.input ? Buffer.from(options.input) : undefined,
|
|
34
22
|
});
|
|
35
23
|
|
|
36
|
-
if (options?.input && proc.stdin) {
|
|
37
|
-
const stdin = proc.stdin as FileSink;
|
|
38
|
-
const payload = typeof options.input === "string" ? new TextEncoder().encode(options.input) : options.input;
|
|
39
|
-
stdin.write(payload);
|
|
40
|
-
const flushed = stdin.flush();
|
|
41
|
-
if (flushed instanceof Promise) {
|
|
42
|
-
await flushed;
|
|
43
|
-
}
|
|
44
|
-
const ended = stdin.end();
|
|
45
|
-
if (ended instanceof Promise) {
|
|
46
|
-
await ended;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
24
|
const [stdout, stderr, exitResult] = await Promise.all([
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
(
|
|
54
|
-
try {
|
|
55
|
-
await proc.exited;
|
|
56
|
-
return proc.exitCode ?? 0;
|
|
57
|
-
} catch (err) {
|
|
58
|
-
if (err && typeof err === "object" && "exitCode" in err) {
|
|
59
|
-
const exitValue = (err as { exitCode?: number }).exitCode;
|
|
60
|
-
if (typeof exitValue === "number") {
|
|
61
|
-
return exitValue;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
throw err instanceof Error ? err : new Error(String(err));
|
|
65
|
-
}
|
|
66
|
-
})(),
|
|
25
|
+
proc.stdout.text(),
|
|
26
|
+
proc.stderr.text(),
|
|
27
|
+
proc.exited.then(() => proc.exitCode ?? 0),
|
|
67
28
|
]);
|
|
68
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
69
|
-
if (options?.signal) {
|
|
70
|
-
options.signal.removeEventListener("abort", onAbort);
|
|
71
|
-
}
|
|
72
29
|
|
|
73
30
|
return {
|
|
74
31
|
stdout,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { nanoid } from "nanoid";
|
|
2
2
|
import { WorktreeError, WorktreeErrorCode } from "./errors";
|
|
3
|
-
import { git,
|
|
3
|
+
import { git, gitWithInput } from "./git";
|
|
4
4
|
import { find, remove, type Worktree } from "./operations";
|
|
5
5
|
|
|
6
6
|
export type CollapseStrategy = "simple" | "merge-base" | "rebase";
|
|
@@ -121,10 +121,10 @@ async function collapseRebase(src: Worktree, dst: Worktree): Promise<string> {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
async function applyDiff(diff: string, targetPath: string): Promise<void> {
|
|
124
|
-
let result = await
|
|
124
|
+
let result = await gitWithInput(["apply"], diff, targetPath);
|
|
125
125
|
if (result.code === 0) return;
|
|
126
126
|
|
|
127
|
-
result = await
|
|
127
|
+
result = await gitWithInput(["apply", "--3way"], diff, targetPath);
|
|
128
128
|
if (result.code === 0) return;
|
|
129
129
|
|
|
130
130
|
throw new WorktreeError(
|
package/src/lib/worktree/git.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import
|
|
2
|
+
import { ptree } from "@oh-my-pi/pi-utils";
|
|
3
3
|
import { execCommand } from "../../core/exec";
|
|
4
4
|
import { WorktreeError, WorktreeErrorCode } from "./errors";
|
|
5
5
|
|
|
@@ -9,32 +9,6 @@ export interface GitResult {
|
|
|
9
9
|
stderr: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
type WritableLike = {
|
|
13
|
-
write: (chunk: string | Uint8Array) => unknown;
|
|
14
|
-
flush?: () => unknown;
|
|
15
|
-
end?: () => unknown;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const textEncoder = new TextEncoder();
|
|
19
|
-
|
|
20
|
-
async function writeStdin(handle: unknown, stdin: string): Promise<void> {
|
|
21
|
-
if (!handle || typeof handle === "number") return;
|
|
22
|
-
if (typeof (handle as WritableStream<Uint8Array>).getWriter === "function") {
|
|
23
|
-
const writer = (handle as WritableStream<Uint8Array>).getWriter();
|
|
24
|
-
try {
|
|
25
|
-
await writer.write(textEncoder.encode(stdin));
|
|
26
|
-
} finally {
|
|
27
|
-
await writer.close();
|
|
28
|
-
}
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const sink = handle as WritableLike;
|
|
33
|
-
sink.write(stdin);
|
|
34
|
-
if (sink.flush) sink.flush();
|
|
35
|
-
if (sink.end) sink.end();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
12
|
/**
|
|
39
13
|
* Execute a git command.
|
|
40
14
|
* @param args - Command arguments (excluding 'git')
|
|
@@ -50,23 +24,15 @@ export async function git(args: string[], cwd?: string): Promise<GitResult> {
|
|
|
50
24
|
* Execute git command with stdin input.
|
|
51
25
|
* Used for piping diffs to `git apply`.
|
|
52
26
|
*/
|
|
53
|
-
export async function
|
|
54
|
-
const proc
|
|
27
|
+
export async function gitWithInput(args: string[], stdin: string, cwd?: string): Promise<GitResult> {
|
|
28
|
+
const proc = ptree.cspawn(["git", ...args], {
|
|
55
29
|
cwd: cwd ?? process.cwd(),
|
|
56
|
-
stdin:
|
|
57
|
-
stdout: "pipe",
|
|
58
|
-
stderr: "pipe",
|
|
30
|
+
stdin: Buffer.from(stdin),
|
|
59
31
|
});
|
|
60
32
|
|
|
61
|
-
await
|
|
62
|
-
|
|
63
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
64
|
-
(proc.stdout as ReadableStream<Uint8Array>).text(),
|
|
65
|
-
(proc.stderr as ReadableStream<Uint8Array>).text(),
|
|
66
|
-
proc.exited,
|
|
67
|
-
]);
|
|
33
|
+
const [stdout, stderr] = await Promise.all([proc.stdout.text(), proc.stderr.text()]);
|
|
68
34
|
|
|
69
|
-
return { code: exitCode ?? 0, stdout, stderr };
|
|
35
|
+
return { code: proc.exitCode ?? 0, stdout, stderr };
|
|
70
36
|
}
|
|
71
37
|
|
|
72
38
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { type CollapseOptions, type CollapseResult, type CollapseStrategy, collapse } from "./collapse";
|
|
2
2
|
export { WORKTREE_BASE } from "./constants";
|
|
3
3
|
export { WorktreeError, WorktreeErrorCode } from "./errors";
|
|
4
|
-
export { getRepoName, getRepoRoot, git, gitWithStdin } from "./git";
|
|
4
|
+
export { getRepoName, getRepoRoot, git, gitWithInput as gitWithStdin } from "./git";
|
|
5
5
|
export { create, find, list, prune, remove, type Worktree, which } from "./operations";
|
|
6
6
|
export {
|
|
7
7
|
cleanupSessions,
|
|
@@ -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,35 +32,40 @@ function selectPreferredImageMimeType(mimeTypes: string[]): string | null {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export async function copyToClipboard(text: string): Promise<void> {
|
|
35
|
-
const
|
|
35
|
+
const p = platform();
|
|
36
|
+
const timeout = 5000;
|
|
36
37
|
|
|
37
|
-
let promise: Promise<void>;
|
|
38
38
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
|
|
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();
|
|
49
50
|
return;
|
|
50
|
-
} else {
|
|
51
|
-
promise = $`xclip -selection clipboard -t text/plain -i ${text}`.quiet().then(() => void 0);
|
|
52
51
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
+
}
|
|
56
60
|
}
|
|
57
61
|
} catch (error) {
|
|
58
|
-
|
|
59
|
-
|
|
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}`);
|
|
60
66
|
}
|
|
61
|
-
throw new Error(`Failed to copy to clipboard: ${
|
|
67
|
+
throw new Error(`Failed to copy to clipboard: ${msg}`);
|
|
62
68
|
}
|
|
63
|
-
await Promise.race([promise, timeout]);
|
|
64
69
|
}
|
|
65
70
|
|
|
66
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
|
-
}
|