@evref-bl/pharo-agent 0.5.4 → 0.5.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pharo-agent",
3
3
  "displayName": "Pharo Agent",
4
- "version": "0.5.4",
4
+ "version": "0.5.5",
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.5.4",
3
+ "version": "0.5.5",
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": "2.1.0",
35
- "@evref-bl/plexus": "0.5.4",
36
- "@evref-bl/plexus-gateway": "0.5.4",
34
+ "@evref-bl/pharo-mcp": "2.1.1",
35
+ "@evref-bl/plexus": "0.5.5",
36
+ "@evref-bl/plexus-gateway": "0.5.5",
37
37
  "@modelcontextprotocol/sdk": "1.32.1"
38
38
  }
39
39
  }
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.5.4",
4
+ "version": "0.5.5",
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.5.4 init --host claude
11
+ npx --yes @evref-bl/pharo-agent@0.5.5 init --host claude
12
12
  ```
13
13
 
14
14
  Name the host this chat runs in: `--host claude` in Claude Code,
package/src/live.js CHANGED
@@ -12,6 +12,7 @@ import { callImageTool } from "./gateway.js";
12
12
  import { readProjectConfig, writeProjectConfig } from "./project.js";
13
13
  import {
14
14
  defaultImageId,
15
+ openedImageIsAlive,
15
16
  readConfig,
16
17
  readOpenImages,
17
18
  resolveImageId,
@@ -22,7 +23,7 @@ import {
22
23
  writeOpenImages,
23
24
  } from "./runtime.js";
24
25
 
25
- export const pifsRelease = "v0.6.1";
26
+ export const pifsRelease = "v0.6.2";
26
27
 
27
28
  // The class-side PIFSServer messages this plugin sends into the image. The
28
29
  // release automation checks that a published PIFS still implements them: a pin
@@ -186,9 +187,18 @@ export function fetchPifsRelease(cloneDirectory, release = pifsRelease) {
186
187
 
187
188
  export async function openAgentImage(projectRoot, imageId) {
188
189
  const resolved = resolveImageId(projectRoot, imageId);
189
- const openImages = readOpenImages(projectRoot);
190
+ let openImages = readOpenImages(projectRoot);
191
+ let restarted = false;
190
192
  if (openImages[resolved]) {
191
- return { alreadyOpen: true, ...openImages[resolved] };
193
+ if (openedImageIsAlive(openImages[resolved])) {
194
+ return { alreadyOpen: true, ...openImages[resolved] };
195
+ }
196
+ // The image this record names has exited. Forget it and open again,
197
+ // rather than hand back an endpoint nothing answers and say it is open.
198
+ const { [resolved]: exited, ...remaining } = openImages;
199
+ openImages = remaining;
200
+ writeOpenImages(projectRoot, openImages);
201
+ restarted = true;
192
202
  }
193
203
  fetchPifsRelease(pifsCloneDirectory(projectRoot));
194
204
  const launcher = new ScopedPharoLauncher({ projectRoot });
@@ -243,6 +253,7 @@ export async function openAgentImage(projectRoot, imageId) {
243
253
  return {
244
254
  imageId: image.id,
245
255
  created,
256
+ ...(restarted ? { restarted } : {}),
246
257
  mountPath,
247
258
  projectionPort,
248
259
  ...(unappliedPin ? { warning: unappliedPin } : {}),
package/src/runtime.js CHANGED
@@ -99,6 +99,32 @@ export function readOpenImage(projectRoot, imageId) {
99
99
  return open;
100
100
  }
101
101
 
102
+ // A record in the project's runtime state says this project opened the image,
103
+ // not that the image is still there. PLexus reclaims a dead image's port and
104
+ // relaunches on the next open, so the only thing standing between a crashed
105
+ // image and a working one is believing the record.
106
+ export function imageProcessIsAlive(pid) {
107
+ if (!Number.isInteger(pid) || pid <= 0) return false;
108
+ try {
109
+ process.kill(pid, 0);
110
+ return true;
111
+ } catch (error) {
112
+ // The process is someone else's, which is still a process.
113
+ return error.code === "EPERM";
114
+ }
115
+ }
116
+
117
+ export function openedImageIsAlive(open) {
118
+ let state;
119
+ try {
120
+ state = readJson(open.statePath);
121
+ } catch {
122
+ return false;
123
+ }
124
+ const image = (state.images ?? []).find((candidate) => candidate.id === open.imageId);
125
+ return image?.status === "running" && imageProcessIsAlive(image.pid);
126
+ }
127
+
102
128
  export function resolveMountPath(projectRoot, config, imageId) {
103
129
  const root = config.mountRoot ?? ".pharo-agent/mounts";
104
130
  return path.resolve(projectRoot, root, imageId);