@evref-bl/pharo-agent 0.3.0 → 0.3.2
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/.claude-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/package.json +4 -4
- package/plugin.json +1 -1
- package/skills/pharo-setup/SKILL.md +2 -2
- package/src/init.js +1 -1
- package/src/live.js +62 -12
- package/src/project.js +2 -2
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ on-demand Pharo workflow skills and references from the pinned MCP template
|
|
|
15
15
|
package. It does not install an agent-host MCP server.
|
|
16
16
|
|
|
17
17
|
`init` acts on the current directory and never searches above it. If that
|
|
18
|
-
directory has no `plexus.
|
|
18
|
+
directory has no `plexus.json`, it creates one that declares the
|
|
19
19
|
project and no image; an existing one is left untouched. It refuses to run
|
|
20
20
|
inside an enclosing PLexus project rather than initializing that project root.
|
|
21
21
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evref-bl/pharo-agent",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Terminal-first Pharo integration for coding agents.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"test": "node --test"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@evref-bl/pharo-mcp": "1.
|
|
35
|
-
"@evref-bl/plexus": "0.
|
|
36
|
-
"@evref-bl/plexus-gateway": "0.
|
|
34
|
+
"@evref-bl/pharo-mcp": "1.5.0",
|
|
35
|
+
"@evref-bl/plexus": "0.4.1",
|
|
36
|
+
"@evref-bl/plexus-gateway": "0.4.1",
|
|
37
37
|
"@modelcontextprotocol/sdk": "1.32.1"
|
|
38
38
|
}
|
|
39
39
|
}
|
package/plugin.json
CHANGED
|
@@ -8,14 +8,14 @@ Run this only when the user asks to enable Pharo tooling in this project.
|
|
|
8
8
|
From the project root, run the released CLI that matches this plugin:
|
|
9
9
|
|
|
10
10
|
```sh
|
|
11
|
-
npx --yes @evref-bl/pharo-agent@0.3.
|
|
11
|
+
npx --yes @evref-bl/pharo-agent@0.3.2 init --host codex
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Use `--host claude` in Claude Code and `--host opencode` in OpenCode.
|
|
15
15
|
Do not configure an MCP server in the agent host. The command adds only a
|
|
16
16
|
conditional instruction block, so Pharo guidance stays out of unrelated work.
|
|
17
17
|
|
|
18
|
-
Setup initializes the current directory, creating `plexus.
|
|
18
|
+
Setup initializes the current directory, creating `plexus.json` with
|
|
19
19
|
no image when it is absent. Register one before the first `pharo-agent open`:
|
|
20
20
|
|
|
21
21
|
```sh
|
package/src/init.js
CHANGED
|
@@ -77,7 +77,7 @@ function guidance() {
|
|
|
77
77
|
function pharoGuide() {
|
|
78
78
|
return `# Pharo project guide
|
|
79
79
|
|
|
80
|
-
\`plexus.
|
|
80
|
+
\`plexus.json\` declares this project's images. A freshly initialized
|
|
81
81
|
project declares none; register one before the first \`pharo-agent open\`:
|
|
82
82
|
|
|
83
83
|
\`\`\`sh
|
package/src/live.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { closeProject, openProject } from "@evref-bl/plexus";
|
|
3
|
+
import { ScopedPharoLauncher, closeProject, openProject } from "@evref-bl/plexus";
|
|
4
4
|
import { callImageTool } from "./gateway.js";
|
|
5
5
|
import {
|
|
6
6
|
defaultImageId,
|
|
@@ -20,13 +20,21 @@ export function pifsStartCode(mountPath, imageName) {
|
|
|
20
20
|
baseline: 'PharoImageFS';
|
|
21
21
|
repository: 'github://Evref-BL/pharo-image-fs:${pifsRelease}/src';
|
|
22
22
|
load: 'Core'.
|
|
23
|
-
(PIFSServer
|
|
23
|
+
((Smalltalk globals at: #PIFSServer)
|
|
24
24
|
startOn: 0
|
|
25
25
|
mountAt: ${smalltalkString(mountPath)} asFileReference
|
|
26
26
|
named: ${smalltalkString(imageName)}) port`;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function resultPrintString(result) {
|
|
30
|
+
// MCP tool results carry the image's answer as JSON inside a text content part.
|
|
31
|
+
if (typeof result === "string") {
|
|
32
|
+
try {
|
|
33
|
+
return resultPrintString(JSON.parse(result));
|
|
34
|
+
} catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
30
38
|
if (!result || typeof result !== "object") return undefined;
|
|
31
39
|
if (typeof result.resultPrintString === "string") return result.resultPrintString;
|
|
32
40
|
for (const value of Object.values(result)) {
|
|
@@ -36,16 +44,56 @@ export function resultPrintString(result) {
|
|
|
36
44
|
return undefined;
|
|
37
45
|
}
|
|
38
46
|
|
|
47
|
+
|
|
48
|
+
export function needsImageCreation(listed, imageId) {
|
|
49
|
+
const summary = listed.images.find((image) => image.imageId === imageId);
|
|
50
|
+
if (!summary) {
|
|
51
|
+
throw new Error(`PLexus does not know image ${imageId}.`);
|
|
52
|
+
}
|
|
53
|
+
// PLexus reports an image it has no runtime state for as declared.
|
|
54
|
+
return summary.status === "declared";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function describeLifecycleFailure(failure) {
|
|
58
|
+
const stderr = failure.launcherResult?.raw?.stderr?.trim();
|
|
59
|
+
return [
|
|
60
|
+
`${failure.imageId}: ${failure.message}`,
|
|
61
|
+
failure.diagnostic,
|
|
62
|
+
failure.action,
|
|
63
|
+
stderr ? `launcher: ${stderr.slice(0, 500)}` : undefined,
|
|
64
|
+
]
|
|
65
|
+
.filter(Boolean)
|
|
66
|
+
.join("\n ");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// openProject and closeProject throw on failure, carrying the failures on the error.
|
|
70
|
+
export async function runLifecycle(action, fallback) {
|
|
71
|
+
try {
|
|
72
|
+
return await action();
|
|
73
|
+
} catch (error) {
|
|
74
|
+
const failures = error?.result?.failures;
|
|
75
|
+
if (!Array.isArray(failures) || failures.length === 0) {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
throw new Error(`${fallback}\n${failures.map(describeLifecycleFailure).join("\n")}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
39
82
|
export async function openAgentImage(projectRoot, imageId) {
|
|
40
83
|
const resolved = resolveImageId(projectRoot, imageId);
|
|
41
84
|
const openImages = readOpenImages(projectRoot);
|
|
42
85
|
if (openImages[resolved]) {
|
|
43
86
|
return { alreadyOpen: true, ...openImages[resolved] };
|
|
44
87
|
}
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
88
|
+
const launcher = new ScopedPharoLauncher({ projectRoot });
|
|
89
|
+
const created = needsImageCreation(launcher.listImages(), resolved);
|
|
90
|
+
if (created) {
|
|
91
|
+
await launcher.createImage(resolved);
|
|
48
92
|
}
|
|
93
|
+
const opened = await runLifecycle(
|
|
94
|
+
() => openProject({ projectRoot, imageIds: [resolved] }),
|
|
95
|
+
`PLexus could not open image ${resolved}.`,
|
|
96
|
+
);
|
|
49
97
|
const { image, mcpServerId } = selectRunningImage(opened.state, resolved);
|
|
50
98
|
const mountPath = resolveMountPath(projectRoot, readConfig(projectRoot), image.id);
|
|
51
99
|
fs.mkdirSync(path.dirname(mountPath), { recursive: true });
|
|
@@ -60,10 +108,12 @@ export async function openAgentImage(projectRoot, imageId) {
|
|
|
60
108
|
});
|
|
61
109
|
const projectionPort = Number(resultPrintString(result));
|
|
62
110
|
if (!Number.isInteger(projectionPort) || projectionPort < 1) {
|
|
63
|
-
throw new Error(
|
|
111
|
+
throw new Error(
|
|
112
|
+
`PIFS did not report a projection port: ${JSON.stringify(result).slice(0, 500)}`,
|
|
113
|
+
);
|
|
64
114
|
}
|
|
65
115
|
writeOpenImages(projectRoot, { ...openImages, [resolved]: { ...open, projectionPort } });
|
|
66
|
-
return { imageId: image.id, mountPath, projectionPort };
|
|
116
|
+
return { imageId: image.id, created, mountPath, projectionPort };
|
|
67
117
|
}
|
|
68
118
|
|
|
69
119
|
async function closeOneImage(projectRoot, openImages, imageId) {
|
|
@@ -71,15 +121,15 @@ async function closeOneImage(projectRoot, openImages, imageId) {
|
|
|
71
121
|
let projectionError;
|
|
72
122
|
try {
|
|
73
123
|
await callImageTool(projectRoot, open, "image_evaluate", {
|
|
74
|
-
code: `PIFSServer stopOn: ${open.projectionPort}`,
|
|
124
|
+
code: `(Smalltalk globals at: #PIFSServer) stopOn: ${open.projectionPort}`,
|
|
75
125
|
});
|
|
76
126
|
} catch (error) {
|
|
77
127
|
projectionError = error;
|
|
78
128
|
}
|
|
79
|
-
const closed = await
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
129
|
+
const closed = await runLifecycle(
|
|
130
|
+
() => closeProject({ projectRoot, imageIds: [imageId] }),
|
|
131
|
+
`PLexus could not close image ${imageId}.`,
|
|
132
|
+
);
|
|
83
133
|
return { stoppedImages: closed.stoppedImages.map((image) => image.id), projectionError };
|
|
84
134
|
}
|
|
85
135
|
|
package/src/project.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
export const projectConfigFileName = "plexus.
|
|
4
|
+
export const projectConfigFileName = "plexus.json";
|
|
5
5
|
|
|
6
6
|
export function findProjectRoot(start = process.cwd()) {
|
|
7
7
|
let directory = path.resolve(start);
|
|
@@ -13,7 +13,7 @@ export function findProjectRoot(start = process.cwd()) {
|
|
|
13
13
|
|
|
14
14
|
const parent = path.dirname(directory);
|
|
15
15
|
if (parent === directory) {
|
|
16
|
-
throw new Error("No plexus.
|
|
16
|
+
throw new Error("No plexus.json found above the current directory");
|
|
17
17
|
}
|
|
18
18
|
directory = parent;
|
|
19
19
|
}
|