@evref-bl/pharo-agent 0.3.1 → 0.3.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/.claude-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/pharo-setup/SKILL.md +1 -1
- package/src/live.js +63 -13
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -8,7 +8,7 @@ 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.3 init --host codex
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Use `--host claude` in Claude Code and `--host opencode` in OpenCode.
|
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,
|
|
@@ -13,20 +13,28 @@ import {
|
|
|
13
13
|
writeOpenImages,
|
|
14
14
|
} from "./runtime.js";
|
|
15
15
|
|
|
16
|
-
export const pifsRelease = "v0.4.
|
|
16
|
+
export const pifsRelease = "v0.4.1";
|
|
17
17
|
|
|
18
18
|
export function pifsStartCode(mountPath, imageName) {
|
|
19
19
|
return `Metacello new
|
|
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
|
|