@oh-my-pi/pi-coding-agent 14.9.2 → 14.9.5
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 +89 -0
- package/package.json +7 -7
- package/scripts/format-prompts.ts +3 -3
- package/src/async/job-manager.ts +66 -9
- package/src/capability/rule.ts +20 -0
- package/src/config/model-registry.ts +13 -0
- package/src/config/model-resolver.ts +8 -2
- package/src/config/prompt-templates.ts +0 -5
- package/src/config/settings-schema.ts +39 -1
- package/src/edit/index.ts +8 -0
- package/src/edit/renderer.ts +6 -1
- package/src/edit/streaming.ts +53 -2
- package/src/eval/eval.lark +10 -31
- package/src/eval/index.ts +1 -0
- package/src/eval/js/context-manager.ts +1 -38
- package/src/eval/js/prelude.txt +0 -2
- package/src/eval/parse.ts +156 -255
- package/src/eval/py/executor.ts +24 -8
- package/src/eval/py/index.ts +1 -0
- package/src/eval/py/prelude.py +11 -80
- package/src/eval/sniff.ts +28 -0
- package/src/export/html/template.css +50 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +229 -17
- package/src/extensibility/plugins/loader.ts +31 -6
- package/src/extensibility/skills.ts +20 -0
- package/src/hashline/constants.ts +20 -0
- package/src/hashline/grammar.lark +16 -23
- package/src/hashline/hash.ts +4 -34
- package/src/hashline/input.ts +16 -2
- package/src/hashline/parser.ts +12 -1
- package/src/internal-urls/agent-protocol.ts +64 -52
- package/src/internal-urls/artifact-protocol.ts +52 -51
- package/src/internal-urls/docs-index.generated.ts +34 -1
- package/src/internal-urls/index.ts +6 -19
- package/src/internal-urls/local-protocol.ts +50 -7
- package/src/internal-urls/mcp-protocol.ts +3 -8
- package/src/internal-urls/memory-protocol.ts +90 -59
- package/src/internal-urls/pi-protocol.ts +1 -0
- package/src/internal-urls/router.ts +40 -23
- package/src/internal-urls/rule-protocol.ts +3 -20
- package/src/internal-urls/skill-protocol.ts +5 -27
- package/src/internal-urls/types.ts +18 -2
- package/src/main.ts +1 -1
- package/src/mcp/manager.ts +17 -0
- package/src/modes/components/session-observer-overlay.ts +2 -2
- package/src/modes/components/tool-execution.ts +6 -0
- package/src/modes/components/tree-selector.ts +4 -0
- package/src/modes/controllers/event-controller.ts +23 -2
- package/src/modes/controllers/mcp-command-controller.ts +7 -10
- package/src/modes/interactive-mode.ts +2 -2
- package/src/modes/theme/theme.ts +27 -27
- package/src/modes/types.ts +1 -1
- package/src/modes/utils/ui-helpers.ts +14 -9
- package/src/prompts/commands/orchestrate.md +1 -0
- package/src/prompts/system/custom-system-prompt.md +0 -2
- package/src/prompts/system/project-prompt.md +10 -0
- package/src/prompts/system/subagent-system-prompt.md +18 -9
- package/src/prompts/system/subagent-user-prompt.md +1 -10
- package/src/prompts/system/system-prompt.md +159 -232
- package/src/prompts/tools/ask.md +0 -1
- package/src/prompts/tools/bash.md +0 -34
- package/src/prompts/tools/eval.md +27 -16
- package/src/prompts/tools/github.md +6 -5
- package/src/prompts/tools/hashline.md +1 -0
- package/src/prompts/tools/job.md +14 -6
- package/src/prompts/tools/task.md +20 -3
- package/src/registry/agent-registry.ts +2 -1
- package/src/sdk.ts +87 -89
- package/src/session/agent-session.ts +107 -37
- package/src/session/artifacts.ts +7 -4
- package/src/session/session-manager.ts +30 -1
- package/src/ssh/connection-manager.ts +32 -16
- package/src/ssh/sshfs-mount.ts +10 -7
- package/src/system-prompt.ts +3 -9
- package/src/task/executor.ts +23 -7
- package/src/task/index.ts +57 -36
- package/src/tool-discovery/tool-index.ts +21 -8
- package/src/tools/ast-edit.ts +3 -2
- package/src/tools/ast-grep.ts +3 -2
- package/src/tools/bash.ts +30 -50
- package/src/tools/browser/tab-supervisor.ts +12 -2
- package/src/tools/eval.ts +59 -44
- package/src/tools/fetch.ts +1 -1
- package/src/tools/gh.ts +140 -4
- package/src/tools/index.ts +12 -11
- package/src/tools/job.ts +48 -12
- package/src/tools/path-utils.ts +21 -1
- package/src/tools/read.ts +74 -31
- package/src/tools/search.ts +16 -3
- package/src/tools/todo-write.ts +1 -1
- package/src/utils/file-display-mode.ts +11 -5
- package/src/web/scrapers/mastodon.ts +1 -1
- package/src/web/scrapers/repology.ts +7 -7
- package/src/internal-urls/jobs-protocol.ts +0 -119
- package/src/task/template.ts +0 -47
- package/src/tools/bash-normalize.ts +0 -107
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Protocol handler for agent:// URLs.
|
|
3
3
|
*
|
|
4
|
-
* Resolves agent output IDs
|
|
4
|
+
* Resolves agent output IDs against the artifacts directories of every active
|
|
5
|
+
* session. Parents and subagents share outputs via this registry: a subagent
|
|
6
|
+
* can read its parent's output IDs because both sessions are registered in
|
|
7
|
+
* the shared context.
|
|
5
8
|
*
|
|
6
9
|
* URL forms:
|
|
7
10
|
* - agent://<id> - Full output content
|
|
@@ -11,27 +14,27 @@
|
|
|
11
14
|
import * as fs from "node:fs/promises";
|
|
12
15
|
import * as path from "node:path";
|
|
13
16
|
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
17
|
+
import { AgentRegistry } from "../registry/agent-registry";
|
|
14
18
|
import { applyQuery, pathToQuery } from "./json-query";
|
|
15
19
|
import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
|
|
16
20
|
|
|
17
|
-
export interface AgentProtocolOptions {
|
|
18
|
-
/**
|
|
19
|
-
* Returns the artifacts directory path, or null if no session.
|
|
20
|
-
* Artifacts directory is the session file path without .jsonl extension.
|
|
21
|
-
*/
|
|
22
|
-
getArtifactsDir: () => string | null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
21
|
/**
|
|
26
|
-
*
|
|
22
|
+
* Snapshot of artifacts dirs for every registered session, deduped.
|
|
23
|
+
*
|
|
24
|
+
* Prefers `sessionManager.getArtifactsDir()` because subagents adopt the
|
|
25
|
+
* parent's manager and report the parent's dir there; dedup then collapses
|
|
26
|
+
* the whole agent tree to one entry. Falls back to the raw session file
|
|
27
|
+
* when no live session reference is attached.
|
|
27
28
|
*/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
function artifactsDirsFromRegistry(): string[] {
|
|
30
|
+
const dirs: string[] = [];
|
|
31
|
+
for (const ref of AgentRegistry.global().list()) {
|
|
32
|
+
const dir =
|
|
33
|
+
ref.session?.sessionManager.getArtifactsDir() ?? (ref.sessionFile ? ref.sessionFile.slice(0, -6) : null);
|
|
34
|
+
if (!dir) continue;
|
|
35
|
+
if (!dirs.includes(dir)) dirs.push(dir);
|
|
34
36
|
}
|
|
37
|
+
return dirs;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
/**
|
|
@@ -42,31 +45,14 @@ async function listAvailableOutputs(artifactsDir: string): Promise<string[]> {
|
|
|
42
45
|
*/
|
|
43
46
|
export class AgentProtocolHandler implements ProtocolHandler {
|
|
44
47
|
readonly scheme = "agent";
|
|
45
|
-
|
|
46
|
-
constructor(private readonly options: AgentProtocolOptions) {}
|
|
48
|
+
readonly immutable = true;
|
|
47
49
|
|
|
48
50
|
async resolve(url: InternalUrl): Promise<InternalResource> {
|
|
49
|
-
const artifactsDir = this.options.getArtifactsDir();
|
|
50
|
-
if (!artifactsDir) {
|
|
51
|
-
throw new Error("No session - agent outputs unavailable");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
await fs.stat(artifactsDir);
|
|
56
|
-
} catch (err) {
|
|
57
|
-
if (isEnoent(err)) {
|
|
58
|
-
throw new Error("No artifacts directory found");
|
|
59
|
-
}
|
|
60
|
-
throw err;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Extract output ID from host
|
|
64
51
|
const outputId = url.rawHost || url.hostname;
|
|
65
52
|
if (!outputId) {
|
|
66
53
|
throw new Error("agent:// URL requires an output ID: agent://<id>");
|
|
67
54
|
}
|
|
68
55
|
|
|
69
|
-
// Check for conflicting extraction methods
|
|
70
56
|
const urlPath = url.pathname;
|
|
71
57
|
const queryParam = url.searchParams.get("q");
|
|
72
58
|
const hasPathExtraction = urlPath && urlPath !== "/" && urlPath !== "";
|
|
@@ -76,28 +62,57 @@ export class AgentProtocolHandler implements ProtocolHandler {
|
|
|
76
62
|
throw new Error("agent:// URL cannot combine path extraction with ?q=");
|
|
77
63
|
}
|
|
78
64
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
65
|
+
const dirs = artifactsDirsFromRegistry();
|
|
66
|
+
|
|
67
|
+
if (dirs.length === 0) {
|
|
68
|
+
throw new Error("No session - agent outputs unavailable");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let foundPath: string | undefined;
|
|
72
|
+
let anyDirExists = false;
|
|
73
|
+
const availableIds = new Set<string>();
|
|
74
|
+
|
|
75
|
+
for (const dir of dirs) {
|
|
76
|
+
try {
|
|
77
|
+
await fs.stat(dir);
|
|
78
|
+
anyDirExists = true;
|
|
79
|
+
} catch (err) {
|
|
80
|
+
if (isEnoent(err)) continue;
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
const candidate = path.join(dir, `${outputId}.md`);
|
|
84
|
+
try {
|
|
85
|
+
await fs.stat(candidate);
|
|
86
|
+
foundPath = candidate;
|
|
87
|
+
break;
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (!isEnoent(err)) throw err;
|
|
90
|
+
try {
|
|
91
|
+
const files = await fs.readdir(dir);
|
|
92
|
+
for (const f of files) {
|
|
93
|
+
if (f.endsWith(".md")) availableIds.add(f.replace(/\.md$/, ""));
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// Listing failures are non-fatal; continue searching.
|
|
97
|
+
}
|
|
88
98
|
}
|
|
89
|
-
throw err;
|
|
90
99
|
}
|
|
91
100
|
|
|
92
|
-
|
|
93
|
-
|
|
101
|
+
if (!anyDirExists) {
|
|
102
|
+
throw new Error("No artifacts directory found");
|
|
103
|
+
}
|
|
94
104
|
|
|
95
|
-
|
|
105
|
+
if (!foundPath) {
|
|
106
|
+
const availableStr = availableIds.size > 0 ? [...availableIds].join(", ") : "none";
|
|
107
|
+
throw new Error(`Not found: ${outputId}\nAvailable: ${availableStr}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const rawContent = await Bun.file(foundPath).text();
|
|
111
|
+
const notes: string[] = [];
|
|
96
112
|
let content = rawContent;
|
|
97
113
|
let contentType: InternalResource["contentType"] = "text/markdown";
|
|
98
114
|
|
|
99
115
|
if (hasPathExtraction || hasQueryExtraction) {
|
|
100
|
-
// Parse JSON
|
|
101
116
|
let jsonValue: unknown;
|
|
102
117
|
try {
|
|
103
118
|
jsonValue = JSON.parse(rawContent);
|
|
@@ -106,9 +121,7 @@ export class AgentProtocolHandler implements ProtocolHandler {
|
|
|
106
121
|
throw new Error(`Output ${outputId} is not valid JSON: ${message}`);
|
|
107
122
|
}
|
|
108
123
|
|
|
109
|
-
// Convert path to query if needed
|
|
110
124
|
const query = hasPathExtraction ? pathToQuery(urlPath) : queryParam!;
|
|
111
|
-
|
|
112
125
|
if (query) {
|
|
113
126
|
const extracted = applyQuery(jsonValue, query);
|
|
114
127
|
try {
|
|
@@ -118,7 +131,6 @@ export class AgentProtocolHandler implements ProtocolHandler {
|
|
|
118
131
|
}
|
|
119
132
|
notes.push(`Extracted: ${query}`);
|
|
120
133
|
} else {
|
|
121
|
-
// Empty path/query means return full JSON
|
|
122
134
|
content = JSON.stringify(jsonValue, null, 2);
|
|
123
135
|
}
|
|
124
136
|
contentType = "application/json";
|
|
@@ -129,7 +141,7 @@ export class AgentProtocolHandler implements ProtocolHandler {
|
|
|
129
141
|
content,
|
|
130
142
|
contentType,
|
|
131
143
|
size: Buffer.byteLength(content, "utf-8"),
|
|
132
|
-
sourcePath:
|
|
144
|
+
sourcePath: foundPath,
|
|
133
145
|
notes,
|
|
134
146
|
};
|
|
135
147
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Protocol handler for artifact:// URLs.
|
|
3
3
|
*
|
|
4
|
-
* Resolves artifact IDs
|
|
5
|
-
* Unlike agent://, artifacts are raw text with no JSON extraction.
|
|
4
|
+
* Resolves artifact IDs against the artifacts directories of every active
|
|
5
|
+
* session. Unlike agent://, artifacts are raw text with no JSON extraction.
|
|
6
6
|
*
|
|
7
7
|
* URL form:
|
|
8
8
|
* - artifact://<id> - Full artifact content
|
|
@@ -12,86 +12,87 @@
|
|
|
12
12
|
import * as fs from "node:fs/promises";
|
|
13
13
|
import * as path from "node:path";
|
|
14
14
|
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
15
|
+
import { AgentRegistry } from "../registry/agent-registry";
|
|
15
16
|
import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
|
|
16
17
|
|
|
17
|
-
export interface ArtifactProtocolOptions {
|
|
18
|
-
/**
|
|
19
|
-
* Returns the artifacts directory path, or null if no session.
|
|
20
|
-
*/
|
|
21
|
-
getArtifactsDir: () => string | null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
18
|
/**
|
|
25
|
-
*
|
|
19
|
+
* Snapshot of artifacts dirs across all registered sessions, deduped.
|
|
20
|
+
*
|
|
21
|
+
* Subagents adopt their parent's `ArtifactManager`, so their
|
|
22
|
+
* `sessionManager.getArtifactsDir()` returns the parent's dir; dedup
|
|
23
|
+
* collapses parent + N subagents to a single entry.
|
|
26
24
|
*/
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
} catch {
|
|
35
|
-
return [];
|
|
25
|
+
function artifactsDirsFromRegistry(): string[] {
|
|
26
|
+
const dirs: string[] = [];
|
|
27
|
+
for (const ref of AgentRegistry.global().list()) {
|
|
28
|
+
const dir =
|
|
29
|
+
ref.session?.sessionManager.getArtifactsDir() ?? (ref.sessionFile ? ref.sessionFile.slice(0, -6) : null);
|
|
30
|
+
if (!dir) continue;
|
|
31
|
+
if (!dirs.includes(dir)) dirs.push(dir);
|
|
36
32
|
}
|
|
33
|
+
return dirs;
|
|
37
34
|
}
|
|
38
35
|
|
|
39
|
-
/**
|
|
40
|
-
* Handler for artifact:// URLs.
|
|
41
|
-
*
|
|
42
|
-
* Resolves numeric artifact IDs to their text content.
|
|
43
|
-
* Artifacts are created by tools when output is truncated.
|
|
44
|
-
*/
|
|
45
36
|
export class ArtifactProtocolHandler implements ProtocolHandler {
|
|
46
37
|
readonly scheme = "artifact";
|
|
47
|
-
|
|
48
|
-
constructor(private readonly options: ArtifactProtocolOptions) {}
|
|
38
|
+
readonly immutable = true;
|
|
49
39
|
|
|
50
40
|
async resolve(url: InternalUrl): Promise<InternalResource> {
|
|
51
|
-
const artifactsDir = this.options.getArtifactsDir();
|
|
52
|
-
if (!artifactsDir) {
|
|
53
|
-
throw new Error("No session - artifacts unavailable");
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Extract artifact ID from host
|
|
57
41
|
const id = url.rawHost || url.hostname;
|
|
58
42
|
if (!id) {
|
|
59
43
|
throw new Error("artifact:// URL requires a numeric ID: artifact://0");
|
|
60
44
|
}
|
|
61
|
-
|
|
62
|
-
// Validate ID is numeric
|
|
63
45
|
if (!/^\d+$/.test(id)) {
|
|
64
46
|
throw new Error(`artifact:// ID must be numeric, got: ${id}`);
|
|
65
47
|
}
|
|
66
48
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
49
|
+
const dirs = artifactsDirsFromRegistry();
|
|
50
|
+
|
|
51
|
+
if (dirs.length === 0) {
|
|
52
|
+
throw new Error("No session - artifacts unavailable");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let foundPath: string | undefined;
|
|
56
|
+
let anyDirExists = false;
|
|
57
|
+
const availableIds = new Set<string>();
|
|
58
|
+
|
|
59
|
+
for (const dir of dirs) {
|
|
60
|
+
let files: string[];
|
|
61
|
+
try {
|
|
62
|
+
files = await fs.readdir(dir);
|
|
63
|
+
anyDirExists = true;
|
|
64
|
+
} catch (err) {
|
|
65
|
+
if (isEnoent(err)) continue;
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
const match = files.find(f => f.startsWith(`${id}.`));
|
|
69
|
+
if (match) {
|
|
70
|
+
foundPath = path.join(dir, match);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
for (const f of files) {
|
|
74
|
+
const m = f.match(/^(\d+)\./);
|
|
75
|
+
if (m) availableIds.add(m[1]);
|
|
74
76
|
}
|
|
75
|
-
throw err;
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
if (!anyDirExists) {
|
|
80
|
+
throw new Error("No artifacts directory found");
|
|
81
|
+
}
|
|
79
82
|
|
|
80
|
-
if (!
|
|
81
|
-
const
|
|
82
|
-
const availableStr =
|
|
83
|
+
if (!foundPath) {
|
|
84
|
+
const sorted = [...availableIds].sort((a, b) => Number(a) - Number(b));
|
|
85
|
+
const availableStr = sorted.length > 0 ? sorted.join(", ") : "none";
|
|
83
86
|
throw new Error(`Artifact ${id} not found. Available: ${availableStr}`);
|
|
84
87
|
}
|
|
85
88
|
|
|
86
|
-
const
|
|
87
|
-
const content = await Bun.file(filePath).text();
|
|
88
|
-
|
|
89
|
+
const content = await Bun.file(foundPath).text();
|
|
89
90
|
return {
|
|
90
91
|
url: url.href,
|
|
91
92
|
content,
|
|
92
93
|
contentType: "text/plain",
|
|
93
94
|
size: Buffer.byteLength(content, "utf-8"),
|
|
94
|
-
sourcePath:
|
|
95
|
+
sourcePath: foundPath,
|
|
95
96
|
};
|
|
96
97
|
}
|
|
97
98
|
}
|