@evref-bl/pharo-agent 0.3.1 → 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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pharo-agent",
3
3
  "displayName": "Pharo Agent",
4
- "version": "0.3.1",
4
+ "version": "0.3.2",
5
5
  "description": "Terminal-first Pharo image setup for coding agents.",
6
6
  "author": {
7
7
  "name": "Evref-BL",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evref-bl/pharo-agent",
3
- "version": "0.3.1",
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",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "pharo-agent",
4
- "version": "0.3.1",
4
+ "version": "0.3.2",
5
5
  "description": "Terminal-first Pharo image setup for coding agents.",
6
6
  "author": {
7
7
  "name": "Evref-BL",
@@ -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.1 init --host codex
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.
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 opened = await openProject({ projectRoot, imageIds: [resolved] });
46
- if (!opened.ok || opened.failures.length > 0) {
47
- throw new Error(opened.failures.map((failure) => failure.message).join("\n") || "PLexus could not open the project image.");
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("PIFS started but did not report its projection port.");
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 closeProject({ projectRoot, imageIds: [imageId] });
80
- if (!closed.ok || closed.failures.length > 0) {
81
- throw new Error(closed.failures.map((failure) => failure.message).join("\n") || "PLexus could not close the project image.");
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