@evref-bl/pharo-agent 0.14.1 → 0.16.0

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.14.1",
4
+ "version": "0.16.0",
5
5
  "description": "Terminal-first Pharo image setup for coding agents.",
6
6
  "author": {
7
7
  "name": "Evref-BL",
package/README.md CHANGED
@@ -87,6 +87,12 @@ Pharo work only, so Pharo instructions stay out of unrelated chats. The guide
87
87
  and what it refers to are maintained by the MCP project and copied from the
88
88
  release this plugin pins. [Hosts](docs/hosts.md) lists the paths it owns.
89
89
 
90
+ **A way to report what got in its way.** The plugin's feedback skill has the
91
+ agent look back over the conversation for tool calls that failed, misled it or
92
+ needed a workaround, and draft issues for the tools' maintainers. It offers
93
+ itself at the end of Pharo work where that happened, searches for existing
94
+ issues first, and files only the drafts you approve.
95
+
90
96
  ## Refreshing after an upgrade
91
97
 
92
98
  Upgrade the plugin the way you installed it, then run the setup command again
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evref-bl/pharo-agent",
3
- "version": "0.14.1",
3
+ "version": "0.16.0",
4
4
  "private": false,
5
5
  "description": "Terminal-first Pharo integration for coding agents.",
6
6
  "license": "Apache-2.0",
@@ -32,8 +32,8 @@
32
32
  "test": "node --test"
33
33
  },
34
34
  "dependencies": {
35
- "@evref-bl/pharo-mcp": "3.4.0",
36
- "@evref-bl/plexus": "0.14.0",
35
+ "@evref-bl/pharo-mcp": "3.5.0",
36
+ "@evref-bl/plexus": "0.15.0",
37
37
  "@modelcontextprotocol/sdk": "1.32.2"
38
38
  },
39
39
  "devDependencies": {
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.14.1",
4
+ "version": "0.16.0",
5
5
  "description": "Terminal-first Pharo image setup for coding agents.",
6
6
  "author": {
7
7
  "name": "Evref-BL",
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: pharo-feedback
3
+ description: Look back on how the Pharo tools served this conversation and propose GitHub issues for what failed, misled you or was missing. Use when the user asks for feedback on the Pharo tools or wants to report a problem with them. At the end of Pharo work in which a tool failed, misled you or needed a workaround, offer it once; never file anything unasked.
4
+ ---
5
+
6
+ # Pharo tool feedback
7
+
8
+ The Pharo tools improve from what their users run into, and you saw this
9
+ conversation's tool calls more closely than anyone. Turn what got in your way
10
+ into issues the maintainers can act on. Issues are public: file only what the
11
+ user approves.
12
+
13
+ ## Look back
14
+
15
+ Go over this conversation's calls to the Pharo tools, whether through this
16
+ plugin's MCP tools or the `pharo-agent` command, and keep what the tools did
17
+ wrong:
18
+
19
+ - a call failed with an error you could not have avoided: an internal error, a
20
+ Smalltalk exception, a timeout;
21
+ - a call failed on your input, and its description, schema or error message
22
+ led you there or did not say what was wrong;
23
+ - a search missed a tool that existed, or found it only after several tries;
24
+ - an answer was wrong, partial, too large to use, or said less than you needed;
25
+ - a warning or the Pharo guidance sent you the wrong way;
26
+ - you fell back to `image_evaluate`, a shell or the files because no tool did
27
+ what you needed;
28
+ - one step took several attempts.
29
+
30
+ Leave out your own slips that a clear error caught, bugs in the user's project,
31
+ and passing trouble such as a network outage, unless the tool handled it badly.
32
+ If the conversation was summarized, say what you can no longer see rather than
33
+ reconstruct it.
34
+
35
+ Then ask the user whether a tool got in their way in a way you did not notice.
36
+
37
+ ## Where each one goes
38
+
39
+ - The image's own tools, from classes, methods and tests to repositories,
40
+ search and evaluation, and the Pharo guidance copied into the project
41
+ (`pharo.AGENTS.md`, the `pharo-*` skills and references): `Evref-BL/MCP`.
42
+ - Everything else this plugin brings, such as the `image_*` tools, the source
43
+ projection, setup, the `pharo-agent` command and the passing of calls between
44
+ the host and an image: `Evref-BL/pharo-agent-plugin`. Its maintainers move an
45
+ issue to the component that owns it.
46
+
47
+ When the project loads an MCP of its own into the image, that project answers
48
+ for the image's tools.
49
+
50
+ ## Before proposing
51
+
52
+ Look for the same problem among each repository's issues, open and closed:
53
+
54
+ ```sh
55
+ gh issue list --repo Evref-BL/MCP --state all --search "<words>"
56
+ ```
57
+
58
+ Where one exists, propose a comment adding what this conversation showed
59
+ instead of a new issue.
60
+
61
+ ## Drafting
62
+
63
+ One issue per problem; findings that share a cause make one issue. Title it
64
+ with what goes wrong, in the tool's own terms. Keep the body short:
65
+
66
+ - **Tool:** its name; `pharo-agent 0.16.0`; the image's Pharo version, which
67
+ `image_list` gives; the host you run in.
68
+ - **What happened:** what you asked for and what came back, the error quoted
69
+ and trimmed.
70
+ - **Expected:** what would have served.
71
+ - **Workaround:** what worked instead, or none.
72
+ - **To reproduce:** the smallest call that shows it.
73
+
74
+ End with: "Reported through the pharo-agent feedback skill."
75
+
76
+ Issues are public. Leave out the project's code, class and package names,
77
+ paths, URLs, and anything that identifies the user or their organization.
78
+ Reproduce on Pharo's own classes, such as `Object` or `OrderedCollection`, or
79
+ describe the shape of what you had. When a problem cannot be shown without the
80
+ project's material, say so and let the user decide.
81
+
82
+ ## Propose, then file
83
+
84
+ Show every draft in full, with its repository, and ask which to file. File only
85
+ those the user names:
86
+
87
+ ```sh
88
+ gh issue create --repo Evref-BL/MCP --title "<title>" --body-file <draft>
89
+ ```
90
+
91
+ Without `gh` or its login, give each draft as a link that opens the form
92
+ already filled, `https://github.com/<owner>/<repo>/issues/new?title=…&body=…`
93
+ with both values URL-encoded, and let the user submit it. A comment on an
94
+ existing issue goes the same way.
95
+
96
+ Report what was filed, with its link.
@@ -5,21 +5,20 @@ description: Enable or refresh Pharo image tooling in the current Pharo project.
5
5
 
6
6
  Run this only when the user asks to enable or refresh Pharo tooling in this project.
7
7
 
8
- From the project root, run the released CLI that matches this plugin:
8
+ From the project root, run the released CLI that matches this plugin, naming
9
+ the host this chat runs in: `--host claude` in Claude Code, `--host codex` in
10
+ Codex, `--host opencode` in OpenCode.
9
11
 
10
12
  ```sh
11
- npx --yes @evref-bl/pharo-agent@0.14.1 init --host claude
13
+ npx --yes @evref-bl/pharo-agent@0.16.0 init --host claude
12
14
  ```
13
15
 
14
- Name the host this chat runs in: `--host claude` in Claude Code,
15
- `--host codex` in Codex, `--host opencode` in OpenCode.
16
- The command writes no host configuration. It adds a conditional instruction
17
- block and this project's own files, so Pharo guidance stays out of unrelated
18
- work.
16
+ It writes the project's Pharo guide, skills and references, and a block in the
17
+ host's guide that points to them; no host configuration. Re-running it after
18
+ an upgrade replaces those files and keeps image declarations and image code.
19
19
 
20
- To get the image's tools as MCP tools in Claude Code, enable this plugin for
21
- this project rather than configuring a server by hand. Commit
22
- `.claude/settings.json`:
20
+ In Claude Code, enable the plugin for this project in `.claude/settings.json`;
21
+ the next session gets the tools:
23
22
 
24
23
  ```json
25
24
  {
@@ -35,26 +34,11 @@ this project rather than configuring a server by hand. Commit
35
34
  }
36
35
  ```
37
36
 
38
- A host reads that file when a session starts, so the tools appear in the next
39
- one. Enabling it per project keeps every other project untouched.
40
-
41
- After upgrading the plugin/CLI, re-run the setup command from the project root
42
- with the same host to refresh the generated guide and copied skills/references.
43
- Those files are overwritten; keep project-specific guidance outside the host's
44
- managed block and outside generated/copied paths. Omit `--mount-root` to retain
45
- existing settings. Image declarations, runtime state, and live image code are
46
- preserved. This refresh does not install a newer package or upgrade image tools.
47
-
48
- Setup initializes the current directory, creating `plexus.json` with
49
- no image when it is absent. Register one before the first
50
- `pharo-agent image start`:
37
+ Then declare the project's first image, its default:
51
38
 
52
39
  ```sh
53
40
  pharo-agent image templates
54
41
  pharo-agent image add dev --template '<launcher template name>'
55
42
  ```
56
43
 
57
- `image templates` names what the launcher offers, with the category of each.
58
-
59
- After setup, read `pharo.AGENTS.md` in this chat before doing Pharo work. A
60
- new chat picks up the conditional project guidance automatically.
44
+ Read `pharo.AGENTS.md` before doing Pharo work.
package/src/help.js CHANGED
@@ -3,75 +3,61 @@ import { hostChoices, hostIds } from "./hosts.js";
3
3
  const commands = {
4
4
  init: {
5
5
  usage: `init --host <${hostChoices()}> [--mount-root <path>]`,
6
- description: "Initialize the current project for the required agent host. Write its managed instruction block,\n"
7
- + "pharo.AGENTS.md, and workflow skills/references. Re-run to refresh those generated files.\n"
8
- + "The mount root defaults to .pharo-agent/mounts; omit --mount-root to keep an existing setting.",
6
+ description: "Write this project's Pharo guide, skills and references for the host. Re-run to refresh\n"
7
+ + "them. --mount-root sets where sources mount, .pharo-agent/mounts by default.",
9
8
  example: `init --host ${hostIds[0]}`,
10
9
  },
11
10
  "image list": {
12
11
  usage: "image list",
13
- description: "List the project's declared images and the default one, with the mount and\n"
14
- + "projection port of each image this project has running. An image this project\n"
15
- + "started that has since exited is listed as exited.",
12
+ description: "List the declared images, the default, and the mount and port of each running one.",
16
13
  example: "image list",
17
14
  },
18
15
  "image templates": {
19
16
  usage: "image templates",
20
- description: "List available launcher templates and their categories. A name in several categories\n"
21
- + "needs --category when passed to image add.",
17
+ description: "List the templates images can be made from, with their categories.",
22
18
  example: "image templates",
23
19
  },
24
20
  "image add": {
25
21
  usage: "image add <id> --template <name> [--category <name>] [--profile <id>]",
26
- description: "Declare an image with a required id and template name; image start creates it later.\n"
27
- + "Use --category for an ambiguous template name and --profile to choose a launcher profile.\n"
28
- + "The first declared image becomes the default.",
29
- example: "image add dev --template '<template name>'",
22
+ description: "Declare an image; image start creates it. Name it after what it is for, like a branch.\n"
23
+ + "The first declared image is the default. --category is for a name two categories offer.",
24
+ example: "image add pretty-print-formatter --template '<template name>'",
30
25
  },
31
26
  "image use": {
32
27
  usage: "image use <id>",
33
- description: "Make the required declared image the project's default. It may already be running.",
28
+ description: "Make an image the default.",
34
29
  example: "image use dev",
35
30
  },
36
31
  "image remove": {
37
32
  usage: "image remove <id>",
38
- description: "Remove the required image declaration. Stop the image first. Its image files are kept.\n"
39
- + "Removing the default image clears the default selection.",
33
+ description: "Drop an image's declaration. Stop it first; its files are kept.",
40
34
  example: "image remove dev",
41
35
  },
42
36
  "image start": {
43
37
  usage: "image start [<id>]",
44
- description: "Create if needed and start an image with its Pharo tools. Omit the id to use the\n"
45
- + "project's default image. Images already running stay running. A source projection is\n"
46
- + "mounted on macOS with fuse-t; elsewhere the image starts without one and says so.\n"
47
- + "Pharo versions outside the MCP support range start without tools or a projection.",
38
+ description: "Create if needed and start an image, the default one without an id. Others keep running.\n"
39
+ + "On macOS with fuse-t its source mounts as files.",
48
40
  example: "image start dev",
49
41
  },
50
42
  "image stop": {
51
43
  usage: "image stop [<id>] [--all]",
52
- description: "Stop the named image and any source projection it has, or the project's default\n"
53
- + "image when no id is given. Use --all to stop every image this project started.",
44
+ description: "Stop an image and its source mount, the default one without an id, or --all of them.",
54
45
  example: "image stop dev",
55
46
  },
56
47
  mcp: {
57
48
  usage: "mcp [--image <id>]",
58
- description: "Serve this project's images as a command-scoped stdio MCP server. Omit\n"
59
- + "--image to use the project's default image. It starts whether or not an image\n"
60
- + "is running: image_start starts one and the image's own tools join the list. The\n"
61
- + "server exits with this command, and the plugin's .mcp.json starts it for a host.",
49
+ description: "Serve this project's images as a stdio MCP server, as the plugin does for a host.",
62
50
  example: "mcp --image dev",
63
51
  },
64
52
  "pharo list": {
65
53
  usage: "pharo list [--image <id>]",
66
- description: "List a running image's advertised tools. Omit --image to use the project's default image.\n"
67
- + "Use tool_search and tool_get to discover and inspect the rest of its catalogue.",
54
+ description: "List a running image's advertised tools. tool_search and tool_get find the rest.",
68
55
  example: "pharo list",
69
56
  },
70
57
  pharo: {
71
58
  usage: "pharo <tool> [arguments-json] [--image <id>]",
72
- description: "Call a tool in a running image. Arguments must be a JSON object and default to {}.\n"
73
- + "Omit --image to use the project's default image. Inspect the tool's schema with tool_get\n"
74
- + "before calling it; a failed tool result makes the command exit unsuccessfully.",
59
+ description: "Call a tool in a running image with a JSON object of arguments, {} by default.\n"
60
+ + "Read its schema with tool_get first.",
75
61
  example: 'pharo tool_get \'{"name":"class_search"}\'',
76
62
  },
77
63
  };
package/src/init.js CHANGED
@@ -110,64 +110,34 @@ function assertRoutingResolves(routing, templates) {
110
110
  function pharoGuide() {
111
111
  return `# Pharo project guide
112
112
 
113
- ${generatedBy}. Edits here are replaced the next time it runs.
113
+ ${generatedBy}. Edits here are replaced the next time it
114
+ runs: re-run it after upgrading the plugin, and keep your own instructions
115
+ elsewhere.
114
116
 
115
- After upgrading the plugin/CLI, re-run \`pharo-agent init --host <host>\` from
116
- the project root with the same host to refresh this guide and the copied skills
117
- and references. Omit \`--mount-root\` to keep existing settings. Image declarations,
118
- runtime state, and live code are preserved. Keep project-specific instructions
119
- outside generated/copied files and outside the host guide's managed block.
120
- Refreshing project files does not install a newer package or upgrade image tools.
117
+ ## Images
121
118
 
122
- \`plexus.json\` declares this project's images. A freshly initialized
123
- project declares none; register one before the first image start. With the
124
- pharo-agent plugin enabled, that is \`image_templates\` and then \`image_add\`,
125
- with no shell. Otherwise:
119
+ \`plexus.json\` declares this workspace's images. With the plugin, use the
120
+ \`image_*\` tools; otherwise:
126
121
 
127
122
  \`\`\`sh
128
123
  pharo-agent image templates
129
- pharo-agent image list
130
- pharo-agent image add dev --template '<launcher template name>'
124
+ pharo-agent image add <id> --template '<template name>'
125
+ pharo-agent image start <id>
131
126
  \`\`\`
132
127
 
133
- \`image templates\` names the templates the launcher offers, with the category
134
- of each; a name two categories offer also needs \`--category <name>\`. Any
135
- version the launcher offers can be registered, and one outside the MCP range
136
- starts without MCP tools or a source projection, saying so.
128
+ The first image declared is the default, the main line. An image takes one
129
+ writer, and making one takes seconds: add a new one for each new subject or
130
+ parallel agent rather than share one, named like a branch
131
+ (\`pretty-print-formatter\`, not \`p15\`). A running image may be another
132
+ session's. Stop an image and remove its declaration when its subject is done.
137
133
 
138
- \`image add\` makes the first registered image the default. \`pharo-agent image
139
- use <id>\` changes the default, and \`pharo-agent image remove <id>\` drops a
140
- declaration. Stop an image before removing its declaration.
134
+ ## Tools
141
135
 
142
- Use \`pharo-agent image start\` before live-image work. On macOS with fuse-t
143
- installed the image's source is also projected under \`.pharo-agent/mounts\`
144
- and can be edited as files; everywhere else the Pharo tools are the way in,
145
- and \`image start\` says so.
146
-
147
- With the pharo-agent plugin enabled for this project, this is all MCP tools:
148
- declare an image with \`image_add\`, start it with \`image_start\`, and call the
149
- image's own tools by name. Without it,
150
- call a tool with \`pharo-agent pharo <tool> [arguments-json]\`, and
151
- \`pharo-agent mcp\` serves the same surface as a command-scoped stdio MCP
152
- server.
153
-
154
- \`pharo-agent pharo list\` names the tools the image advertises, which are the
155
- common ones rather than all of it. Find the rest through the catalogue rather
156
- than guessing at a name, then call one the same way as any other:
157
-
158
- \`\`\`sh
159
- pharo-agent pharo tool_search '{"query":"repository commit"}'
160
- pharo-agent pharo tool_get '{"name":"repository_commit"}'
161
- pharo-agent pharo repository_commit '{"name":"MCP","message":"..."}'
162
- \`\`\`
163
-
164
- Several images can run at once. \`pharo-agent image start <id>\` and
165
- \`pharo-agent image stop <id>\` name their image, \`pharo\` and \`mcp\` take
166
- \`--image <id>\`, and without either the default image answers.
167
- \`pharo-agent image list\` says which are running, and
168
- \`pharo-agent image stop --all\` stops every one this project started.
169
-
170
- Image, Tonel, Iceberg, disk Git, and host processes are separate states.
136
+ With the plugin, an image's tools are MCP tools. Otherwise call one with
137
+ \`pharo-agent pharo <tool> [arguments-json] [--image <id>]\`, or serve them
138
+ with \`pharo-agent mcp\`. An image lists its common tools; \`tool_search\` and
139
+ \`tool_get\` find the rest. On macOS with fuse-t, \`image start\` also mounts
140
+ the source under \`.pharo-agent/mounts\`.
171
141
 
172
142
  ${pharoRouting()}
173
143
  `;
package/src/live.js CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  writeRunningImages,
23
23
  } from "./runtime.js";
24
24
 
25
- export const pifsRelease = "v0.10.1";
25
+ export const pifsRelease = "v0.10.3";
26
26
 
27
27
  // fuse-t ships as a macOS installer package, so it lands in /usr/local
28
28
  // whatever Homebrew's prefix is. The unversioned symlink is what the daemon
package/src/mcpServer.js CHANGED
@@ -19,18 +19,18 @@ import { readRunningImage } from "./runtime.js";
19
19
  // defers the rest, so this is what decides whether Pharo tools are searched
20
20
  // for at all. It is the conditional guidance the generated guide gives,
21
21
  // written where the host can act on it.
22
- export const serverInstructions = `Live Pharo images for this project: reading and writing Smalltalk code in the
23
- image, running tests, finding senders and implementors, applying critiques,
24
- and projecting image source as files on disk.
22
+ export const serverInstructions = `Live Pharo images for this project: reading and changing code, running tests,
23
+ searching and critiquing it.
25
24
 
26
- Search these tools whenever the task involves Pharo, Smalltalk, a Tonel
27
- package, or a .st file. Start an image with image_start before calling the
28
- image's own tools, which are listed once an image answers; image_list says
29
- what is already running. A project that declares no image yet needs
30
- image_templates and image_add first.
25
+ Search these tools for any Pharo, Smalltalk, Tonel or .st work. Start an
26
+ image with image_start before calling its tools; a project with no image needs
27
+ image_templates and image_add first. An image lists its common tools;
28
+ tool_search and tool_get find the rest.
31
29
 
32
- The image advertises the common tools rather than all of it. Use tool_search
33
- and tool_get to find the rest, then call one by name like any other.`;
30
+ An image takes one writer, and making one takes seconds: image_add a new one
31
+ for each new subject or parallel agent rather than share one, named like a
32
+ branch (pretty-print-formatter, not p15). The default image is the main line.
33
+ A running image may be another session's.`;
34
34
 
35
35
  // This server's own tools, in the order a project meets them: declare an
36
36
  // image, start it, and the image's tools join the list. Listed whether or not
@@ -40,34 +40,28 @@ and tool_get to find the rest, then call one by name like any other.`;
40
40
  const projectTools = [
41
41
  {
42
42
  name: "image_templates",
43
- description:
44
- "List the images Pharo Launcher can create, with the category of each. "
45
- + "A template name that two categories offer is flagged, and needs its "
46
- + "category when it is declared.",
43
+ description: "List the templates images can be made from, with their categories.",
47
44
  inputSchema: { type: "object", properties: {} },
48
45
  },
49
46
  {
50
47
  name: "image_add",
51
48
  description:
52
- "Declare an image in this project, from a template image_templates "
53
- + "names. Declaring does not create the image: the first image_start "
54
- + "does, from this template. The first image declared becomes the "
55
- + "project's default.",
49
+ "Declare an image made from a template; image_start creates it. The "
50
+ + "first image declared is the default.",
56
51
  inputSchema: {
57
52
  type: "object",
58
53
  properties: {
59
54
  id: {
60
55
  type: "string",
61
- description: "Short id this project calls the image by, such as dev.",
56
+ description: "What the image is for, named like a branch.",
62
57
  },
63
58
  template: {
64
59
  type: "string",
65
- description: "Template name, exactly as image_templates gives it.",
60
+ description: "Template name, as image_templates gives it.",
66
61
  },
67
62
  category: {
68
63
  type: "string",
69
- description:
70
- "Template category. Needed only for a name two categories offer.",
64
+ description: "Template category, for a name two categories offer.",
71
65
  },
72
66
  profile: {
73
67
  type: "string",
@@ -80,42 +74,36 @@ const projectTools = [
80
74
  {
81
75
  name: "image_start",
82
76
  description:
83
- "Start one of this project's Pharo images, creating it from its template "
84
- + "when it does not exist yet. The image's own tools are listed once it "
85
- + "answers. On macOS with fuse-t its source also mounts as files; "
86
- + "elsewhere the image starts without that and says so.",
77
+ "Start an image, creating it the first time. Its tools are listed once "
78
+ + "it answers. On macOS with fuse-t its source also mounts as files.",
87
79
  inputSchema: {
88
80
  type: "object",
89
81
  properties: {
90
82
  image: {
91
83
  type: "string",
92
- description: "Image id to start. Defaults to the project's default image.",
84
+ description: "Image id. Defaults to the default image.",
93
85
  },
94
86
  },
95
87
  },
96
88
  },
97
89
  {
98
90
  name: "image_list",
99
- description:
100
- "List this project's declared images, which are running, and the route "
101
- + "and mount of each running one. An image that was started and has "
102
- + "exited since is listed as exited; image_start starts it again.",
91
+ description: "List the declared images, which are running, and the route and mount of each.",
103
92
  inputSchema: { type: "object", properties: {} },
104
93
  },
105
94
  {
106
95
  name: "image_stop",
107
- description:
108
- "Stop a running image, and any source projection this project started for it.",
96
+ description: "Stop an image and its source mount.",
109
97
  inputSchema: {
110
98
  type: "object",
111
99
  properties: {
112
100
  image: {
113
101
  type: "string",
114
- description: "Image id to stop. Defaults to the project's default image.",
102
+ description: "Image id. Defaults to the default image.",
115
103
  },
116
104
  all: {
117
105
  type: "boolean",
118
- description: "Stop every image this project started, instead of one.",
106
+ description: "Stop every image this project started.",
119
107
  },
120
108
  },
121
109
  },