@vention/vention-skills 0.9.16 → 0.9.19

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,6 +1,6 @@
1
1
  {
2
2
  "name": "vention",
3
- "version": "0.9.16",
3
+ "version": "0.9.19",
4
4
  "description": "Vention skills for coding agents: write MachineLogic applications, build machine designs, and diagnose deployed machines.",
5
5
  "author": {
6
6
  "name": "Vention"
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vention skills
2
2
 
3
- Version 0.9.16. Skills for coding agents that work with Vention machines, packaged as one plugin, `vention`, in the `vention-skills` marketplace.
3
+ Version 0.9.19. Skills for coding agents that work with Vention machines, packaged as one plugin, `vention`, in the `vention-skills` marketplace.
4
4
 
5
5
  | Skill | What it does |
6
6
  | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vention/vention-skills",
3
- "version": "0.9.16",
3
+ "version": "0.9.19",
4
4
  "description": "Vention skills for coding agents: write MachineLogic applications, build machine designs, and diagnose deployed machines.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -16,4 +16,4 @@
16
16
  "registry": "https://registry.npmjs.org/"
17
17
  },
18
18
  "dependencies": {}
19
- }
19
+ }
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": "vention",
4
- "version": "0.9.16",
4
+ "version": "0.9.19",
5
5
  "description": "Vention skills for coding agents: write MachineLogic applications, build machine designs, and diagnose deployed machines.",
6
6
  "author": {
7
7
  "name": "Vention"
@@ -3,7 +3,7 @@ name: vention-design
3
3
  description: Create, build and edit Vention machine designs and design graphs for agentic layout. Use when the user wants to start a new design, lay out an automation cell, add or reposition components in a Vention design, generate a machine layout from a description, add or place a floor plan image under a design, list a design's versions, or inspect an existing design graph. Trigger on MachineBuilder designs, design graphs, assembly graphs, extrusions, actuators, robot mounting, cell footprints, floor plans, or a machine-builder URL.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.9.16"
6
+ version: "0.9.19"
7
7
  author: Vention
8
8
  ---
9
9
 
@@ -3,7 +3,7 @@ name: vention-machine-logic
3
3
  description: Write, edit, validate, and deploy MachineLogic Python applications for Vention machines with the machine-logic-sdk and the Vention CLI. Load it before writing or changing any Python that talks to a Vention machine, however short, since device names and version-specific SDK calls come from here. Use to program a Vention machine or MachineMotion controller, drive actuators, conveyors, robots, pneumatics, or digital I/O, change an existing application, build a state machine, debug from logs, or pull, push, or link with the Vention CLI (vention or vn). Trigger on MachineLogic, machine-logic-sdk, MachineMotion, MachineBuilder applications, the Vention CLI, or a directory holding .machine-code-app-directory-info.json. Printing what the machine or robot holds now (positions, taught targets, calibration, I/O) is a program: files on disk hold only as-designed values. Not for explaining an existing machine or its configuration in words when no program is asked for, nor for Python that never touches the machine.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.9.16"
6
+ version: "0.9.19"
7
7
  machine-logic-sdk: ">=3.0,<4"
8
8
  author: Vention
9
9
  ---
@@ -242,9 +242,11 @@ Each example is a standalone `main.py` whose module docstring names the device n
242
242
 
243
243
  2. Run `uv venv --python 3.10 && uv pip install -r requirements-dev.txt`, then `.venv/bin/pyright`. The venv runs the target's Python and the SDK the application pins, so a wrong name or signature shows up against what the target runs. The first pyright run may download Node if none is on the PATH, so a slow or offline first run is not a type error. Fix every error and run it again until it is clean.
244
244
  3. If `uv` is missing or the install fails, tell the user the code is unvalidated. Never report a check you could not run as passed.
245
- 4. `vention push`, then run the application on the digital twin from MachineBuilder before any hardware run.
246
- 5. Read the output with `vention logs`. MachineLogic failures name the device and the reason; read the log before proposing a change.
247
- 6. Show the user the diff before pushing over an application they did not write in this session.
245
+ 4. `vention push`, then run the application on the digital twin from MachineBuilder before any hardware run. One or two full cycles prove the cycle; each extra one costs a cycle time and shows nothing the first two did not.
246
+ 5. Read the output with `vention logs` once the run ends, rather than following it while the cycles play. MachineLogic failures name the device and the reason; read the log before proposing a change.
247
+ 6. Check a motion from the log, not by watching the twin: `print` the robot's joint angles or the axis position at each step, then compare them with what the step should reach.
248
+ 7. Pressing the emergency stop on the digital twin's pendant does nothing, so skip that test there. Tell the user the e-stop path is untested until the first run on the real cell.
249
+ 8. Show the user the diff before pushing over an application they did not write in this session.
248
250
 
249
251
  ## Deploying to a real cell
250
252
 
@@ -3,7 +3,7 @@ name: vention-machine-logic-hmi
3
3
  description: Build or edit the custom operator HMI of a MachineLogic application, the React touch screen in customui/ that runs in MachineBuilder and on the MachineMotion pendant, wired to the application's Python process through vention-communication. Use when the user asks for an operator screen, a custom HMI or UI, a touch panel, start and stop buttons, live machine state on screen, or recipes an operator picks, for a Vention machine or inside a directory holding .machine-code-app-directory-info.json. Uses @vention/machine-ui, @vention/machine-apps-components and @vention/machine-logic-ui-sdk. Do not use for the no-code HMI Builder, or for the Python motion logic alone (that is vention-machine-logic).
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.9.16"
6
+ version: "0.9.19"
7
7
  author: Vention
8
8
  ---
9
9
 
@@ -152,7 +152,7 @@ The machine-apps-components README is behind its code: `NavigationBar` takes `Na
152
152
 
153
153
  - Wrap the app in `machineUiHmiTheme`.
154
154
  - Give the app an Operate page at `/`, any setup pages it needs (such as Recipes), and a Logs page: a `LogsPanel` fed by the Events stream, as the example's `logs-page.tsx` does, where the operator finds out why a cycle faulted. Nav labels name the page and never echo a top-bar action, and their icons don't suggest one. Never "Run", "Start" or a play icon: beside the top bar's Start, the operator reads it as a second Start button.
155
- - The Operate page is what the operator watches during a run, so it reads from across the cell. Copy the example's `operate-page.tsx` and adapt it rather than starting from a blank page. Top to bottom: a heading saying what the station is doing or the recipe running, the run's segmented progress bar, the settings for the next run (disabled while one runs), and one short paragraph on what Start and Stop do. Lay it out with `Box` or `Stack` gaps, each readout a label above its value in theme text variants. Never set `fontSize` or `lineHeight`: a value sized by hand overlaps its label.
155
+ - The Operate page is what the operator watches during a run, so it reads from across the cell. Copy the example's `operate-page.tsx` and adapt it rather than starting from a blank page. While a run is on, show from top to bottom: the step in progress named inside a ring that fills against the last cycle's time, beside the recipe and the cycles done and rate; the cycle's steps as a row of tiles, the current one lit; and the run's progress bar. Copy the example's StepRing, StepRail and CycleProgress as they are, and publish the step and the cycle's elapsed time on the status stream as its enter_step method does. A single `VentionProgressBar` cannot mark the cycles a Stop cancels. Below them go the settings for the next run (disabled while one runs) and one short paragraph on what Start and Stop do. Leave out what the operator can neither change nor act on, such as axis stops, taught positions or device names: they are the program's constants, not the run's. Lay it out with `Box` or `Stack` gaps, each readout a label above its value in theme text variants. Never set `fontSize` or `lineHeight`: a value sized by hand overlaps its label.
156
156
  - Show each piece of information in one place only. The `StatusTopBar` already shows the state, so a page never repeats it, not even as a status indicator or an alert titled with the same word. A page adds only what the bar cannot say: the cause of a fault, what happens next, what to do.
157
157
  - The screen fits the pendant without scrolling: keep `body { margin: 0 }` from the example's `index.html`, or the full-viewport root overflows it. Only the page area between the two bars scrolls.
158
158
  - Exactly one way back to Control Center: the `NavigationBar`'s own button, which confirms before leaving. Keep `ui.showDefaultBackToControlCenterButton` `false` in `project.json`, or the pendant lays a second one over the screen.
@@ -205,7 +205,7 @@ The HMI drives physical machinery that can injure someone.
205
205
  - Polling for live state instead of a stream.
206
206
  - A raw `fetch` to `/rpc`, or any request that skips the generated client.
207
207
  - Editing or committing `src/gen/`, or forgetting to re-emit `proto/app.proto` after a Python change.
208
- - Dropping `chmod -R g+w` from the build scripts. The engine deletes the application as another user, and it cannot remove files the build left group-read-only.
208
+ - Dropping `chmod -R g+w` from the build scripts, or letting it fail the build. The engine deletes the application as another user, and it cannot remove files the build left group-read-only. That user also owns what the last build left in `ui/`, so the example keeps going with `|| true` where `chmod` cannot change them.
209
209
  - A stream named after its payload model: `rpc Status` returning `Status` fails in buf. Name the payload `StationStatus`.
210
210
  - A storage field with no zero default, or a storage write with no `actor`.
211
211
  - A process name in `client.ts` that differs from `project.json`.
@@ -4,7 +4,7 @@
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "prebuild": "rm -rf src/gen && buf generate ../proto && chmod -R g+w src/gen",
7
- "build": "esbuild src/main.tsx --bundle --outdir=../ui/assets --format=esm --target=esnext --loader:.tsx=tsx --loader:.ts=ts --loader:.css=css --splitting --entry-names=main --chunk-names=[name]-[hash] && sed 's|/src/main.tsx|./assets/main.js|g' index.html | sed 's|</head>|<link rel=\"stylesheet\" href=\"./assets/main.css\"></head>|g' > ../ui/index.html && chmod -R g+w ../ui",
7
+ "build": "esbuild src/main.tsx --bundle --outdir=../ui/assets --format=esm --target=esnext --loader:.tsx=tsx --loader:.ts=ts --loader:.css=css --splitting --entry-names=main --chunk-names=[name]-[hash] && sed 's|/src/main.tsx|./assets/main.js|g' index.html | sed 's|</head>|<link rel=\"stylesheet\" href=\"./assets/main.css\"></head>|g' > ../ui/index.html && (chmod -R g+w ../ui 2>/dev/null || true)",
8
8
  "typecheck": "tsc --noEmit"
9
9
  },
10
10
  "dependencies": {
@@ -3,7 +3,7 @@ import { COLORS, VentionAlert, VentionSelect } from "@vention/machine-ui"
3
3
  import { useEffect, useState } from "react"
4
4
  import { client } from "../client"
5
5
  import type { Recipe, StationStatus } from "../gen/app_pb"
6
- import { canStart, errorText, isStopping } from "../station"
6
+ import { STEPS, canStart, errorText, isStopping } from "../station"
7
7
 
8
8
  interface OperatePageProps {
9
9
  status: StationStatus | undefined
@@ -14,6 +14,108 @@ interface OperatePageProps {
14
14
  }
15
15
 
16
16
  const HATCHED = `repeating-linear-gradient(45deg, ${COLORS.slate[100]} 0 8px, ${COLORS.slate[200]} 8px 16px)`
17
+ const RING_RADIUS = 52
18
+ const RING_LENGTH = 2 * Math.PI * RING_RADIUS
19
+ const TICK_MS = 250
20
+
21
+ /** Seconds into the cycle, counted on from the last status so the ring moves between heartbeats. */
22
+ function useCycleElapsed(status: StationStatus | undefined): number {
23
+ const [received, setReceived] = useState({ seconds: 0, at: 0 })
24
+ const [now, setNow] = useState(() => performance.now())
25
+ const running = status !== undefined && status.step !== ""
26
+ useEffect(() => setReceived({ seconds: status?.cycleElapsedSeconds ?? 0, at: performance.now() }), [status])
27
+ useEffect(() => {
28
+ if (!running) return
29
+ const timer = setInterval(() => setNow(performance.now()), TICK_MS)
30
+ return () => clearInterval(timer)
31
+ }, [running])
32
+ return running ? received.seconds + Math.max(0, now - received.at) / 1000 : 0
33
+ }
34
+
35
+ /** The step in progress, inside a ring that fills as the cycle runs against the last cycle's time. */
36
+ function StepRing({ step, elapsed, expected, stopping }: { step: string; elapsed: number; expected: number; stopping: boolean }) {
37
+ const current = STEPS.find(candidate => candidate.name === step)
38
+ const StepIcon = current?.icon
39
+ const filled = expected > 0 ? Math.min(elapsed / expected, 1) : 0
40
+ const color = stopping ? COLORS.amber[500] : COLORS.blue[500]
41
+ return (
42
+ <Box sx={{ position: "relative", width: 220, height: 220, flexShrink: 0 }}>
43
+ <svg viewBox="0 0 120 120" width={220} height={220} aria-hidden="true">
44
+ <circle cx={60} cy={60} r={RING_RADIUS} fill="none" stroke={COLORS.slate[100]} strokeWidth={8} />
45
+ <circle
46
+ cx={60}
47
+ cy={60}
48
+ r={RING_RADIUS}
49
+ fill="none"
50
+ stroke={color}
51
+ strokeWidth={8}
52
+ strokeLinecap="round"
53
+ strokeDasharray={`${filled * RING_LENGTH} ${RING_LENGTH}`}
54
+ transform="rotate(-90 60 60)"
55
+ />
56
+ </svg>
57
+ <Box
58
+ sx={{
59
+ position: "absolute",
60
+ inset: 0,
61
+ display: "flex",
62
+ flexDirection: "column",
63
+ alignItems: "center",
64
+ justifyContent: "center",
65
+ gap: 1,
66
+ }}
67
+ >
68
+ {StepIcon && <StepIcon size={36} color={color} />}
69
+ <Typography variant="heading18SemiBold">{current?.label ?? step}</Typography>
70
+ <Typography variant="paragraph18Medium">
71
+ {Math.floor(elapsed)} s{expected > 0 && ` of ~${Math.round(expected)} s`}
72
+ </Typography>
73
+ </Box>
74
+ </Box>
75
+ )
76
+ }
77
+
78
+ /** The cycle's steps in order: done ones green, the current one lit, the rest grey. */
79
+ function StepRail({ step, stopping }: { step: string; stopping: boolean }) {
80
+ const currentIndex = STEPS.findIndex(candidate => candidate.name === step)
81
+ return (
82
+ <Box sx={{ display: "grid", gridTemplateColumns: `repeat(${STEPS.length}, minmax(0, 1fr))`, gap: 4 }}>
83
+ {STEPS.map(({ name, icon: StepIcon }, index) => {
84
+ const done = index < currentIndex
85
+ const current = index === currentIndex
86
+ const background = current ? (stopping ? COLORS.amber[500] : COLORS.blue[500]) : done ? COLORS.green[100] : COLORS.slate[100]
87
+ const iconColor = current ? "white" : done ? COLORS.green[700] : COLORS.slate[400]
88
+ return (
89
+ <Box key={name} sx={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 2 }}>
90
+ <Box
91
+ sx={{
92
+ width: "100%",
93
+ height: 64,
94
+ borderRadius: 2,
95
+ background,
96
+ display: "flex",
97
+ alignItems: "center",
98
+ justifyContent: "center",
99
+ }}
100
+ >
101
+ <StepIcon size={32} color={iconColor} />
102
+ </Box>
103
+ <Typography variant={current ? "heading18SemiBold" : "paragraph18Medium"}>{name}</Typography>
104
+ </Box>
105
+ )
106
+ })}
107
+ </Box>
108
+ )
109
+ }
110
+
111
+ function Counter({ label, value }: { label: string; value: string }) {
112
+ return (
113
+ <Box sx={{ display: "flex", flexDirection: "column", gap: 1, padding: 4, borderRadius: 2, background: COLORS.slate[50] }}>
114
+ <Typography variant="paragraph18Medium">{label}</Typography>
115
+ <Typography variant="heading24SemiBold">{value}</Typography>
116
+ </Box>
117
+ )
118
+ }
17
119
 
18
120
  /** Done cycles green, the current one amber while stopping, and the cycles Stop cancels hatched. */
19
121
  function CycleProgress({ cycle, cycleCount, stopping }: { cycle: number; cycleCount: number; stopping: boolean }) {
@@ -37,6 +139,8 @@ function CycleProgress({ cycle, cycleCount, stopping }: { cycle: number; cycleCo
37
139
  export function OperatePage({ status, connected, recipe, onRecipeChange, onError }: OperatePageProps) {
38
140
  const [recipes, setRecipes] = useState<Recipe[]>([])
39
141
  const stopping = isStopping(status, connected)
142
+ const elapsed = useCycleElapsed(status)
143
+ const running = status !== undefined && status.state.startsWith("Station_") && status.cycle > 0
40
144
 
41
145
  useEffect(() => {
42
146
  if (!connected) return
@@ -61,18 +165,33 @@ export function OperatePage({ status, connected, recipe, onRecipeChange, onError
61
165
  onPrimaryButtonClick={reset}
62
166
  />
63
167
  )}
64
- <Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
65
- <Box sx={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 4 }}>
168
+ {running ? (
169
+ <Box sx={{ display: "flex", flexDirection: "column", gap: 5 }}>
170
+ <Box sx={{ display: "flex", alignItems: "center", gap: 5 }}>
171
+ <StepRing step={status.step} elapsed={elapsed} expected={status.lastCycleSeconds} stopping={stopping} />
172
+ <Box sx={{ flex: 1, minWidth: 0, display: "flex", flexDirection: "column", gap: 4 }}>
173
+ <Typography variant="heading24SemiBold">{status.recipeName}</Typography>
174
+ {stopping && <Typography variant="paragraph18Medium">The emergency stop halts at once.</Typography>}
175
+ <Box sx={{ display: "grid", gridTemplateColumns: "repeat(2, minmax(0, 1fr))", gap: 4 }}>
176
+ <Counter label="Cycles done" value={`${status.cycle - 1} of ${status.cycleCount}`} />
177
+ <Counter
178
+ label="Rate"
179
+ value={status.lastCycleSeconds > 0 ? `${Math.round(3600 / status.lastCycleSeconds)} parts/h` : "After cycle 1"}
180
+ />
181
+ </Box>
182
+ </Box>
183
+ </Box>
184
+ <StepRail step={status.step} stopping={stopping} />
185
+ <CycleProgress cycle={status.cycle} cycleCount={status.cycleCount} stopping={stopping} />
186
+ </Box>
187
+ ) : (
188
+ <Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
66
189
  <Typography variant="heading24SemiBold">{status?.recipeName || "No recipe running"}</Typography>
67
- {stopping && <Typography variant="paragraph18Medium">The emergency stop halts at once.</Typography>}
190
+ <Typography variant="paragraph18Medium">
191
+ Last cycle: {status && status.lastCycleSeconds > 0 ? `${status.lastCycleSeconds.toFixed(1)} s` : "none yet"}
192
+ </Typography>
68
193
  </Box>
69
- {status?.state.startsWith("Station_") && status.cycle > 0 && (
70
- <CycleProgress cycle={status.cycle} cycleCount={status.cycleCount} stopping={stopping} />
71
- )}
72
- <Typography variant="paragraph18Medium">
73
- Last cycle: {status && status.lastCycleSeconds > 0 ? `${status.lastCycleSeconds.toFixed(1)} s` : "none yet"}
74
- </Typography>
75
- </Box>
194
+ )}
76
195
  <VentionSelect
77
196
  size="xx-large"
78
197
  variant="outlined"
@@ -1,10 +1,19 @@
1
1
  import { ConnectError } from "@connectrpc/connect"
2
+ import { IconArrowRight, IconHourglass, IconLock, IconLockOpen, type Icon } from "@tabler/icons-react"
2
3
  import { COLORS } from "@vention/machine-ui"
3
4
  import type { StationStatus } from "./gen/app_pb"
4
5
 
5
6
  // The storage RPCs record who changed each recipe.
6
7
  export const ACTOR = "operator"
7
8
 
9
+ // One per enter_step call in server.py, in the same order and under the same name.
10
+ export const STEPS: { name: string; label: string; icon: Icon }[] = [
11
+ { name: "Index", label: "Indexing the part", icon: IconArrowRight },
12
+ { name: "Clamp", label: "Clamping", icon: IconLock },
13
+ { name: "Dwell", label: "Dwelling", icon: IconHourglass },
14
+ { name: "Release", label: "Releasing", icon: IconLockOpen },
15
+ ]
16
+
8
17
  type StateDisplay = { label: string; color: string }
9
18
 
10
19
  const STATE_DISPLAY: Record<string, StateDisplay> = {
@@ -158,8 +158,10 @@ message StationStatus {
158
158
  string recipe_name = 2;
159
159
  int32 cycle = 3;
160
160
  int32 cycle_count = 4;
161
- double last_cycle_seconds = 5;
162
- string fault = 6;
161
+ string step = 5;
162
+ double cycle_elapsed_seconds = 6;
163
+ double last_cycle_seconds = 7;
164
+ string fault = 8;
163
165
  }
164
166
 
165
167
  message Event {
@@ -63,6 +63,8 @@ class StationStatus(BaseModel):
63
63
  recipe_name: str
64
64
  cycle: int
65
65
  cycle_count: int
66
+ step: str
67
+ cycle_elapsed_seconds: float
66
68
  last_cycle_seconds: float
67
69
  fault: str
68
70
 
@@ -89,6 +91,8 @@ class IndexingStation(StateMachine):
89
91
  self.machine: Optional[Machine] = None
90
92
  self.recipe: Optional[Recipe] = None
91
93
  self.cycle = 0
94
+ self.step = ""
95
+ self.cycle_started = 0.0
92
96
  self.last_cycle_seconds = 0.0
93
97
  self.fault_message = ""
94
98
  self.stop_requested = False
@@ -100,6 +104,8 @@ class IndexingStation(StateMachine):
100
104
  recipe_name=recipe.name if recipe else "",
101
105
  cycle=self.cycle,
102
106
  cycle_count=recipe.cycle_count if recipe else 0,
107
+ step=self.step,
108
+ cycle_elapsed_seconds=time.monotonic() - self.cycle_started if self.step else 0.0,
103
109
  last_cycle_seconds=self.last_cycle_seconds,
104
110
  fault=self.fault_message,
105
111
  )
@@ -111,6 +117,7 @@ class IndexingStation(StateMachine):
111
117
  @on_enter_state(BaseStates.READY.value)
112
118
  def enter_ready(self, _: object) -> None:
113
119
  self.fault_message = ""
120
+ self.step = ""
114
121
 
115
122
  @on_enter_state(States.station.running)
116
123
  def enter_running(self, _: object) -> None:
@@ -151,6 +158,11 @@ class IndexingStation(StateMachine):
151
158
  except Exception as error:
152
159
  await publish_event("error", "Clamp release failed", f"{error}. Check the clamp is open before the next run.")
153
160
 
161
+ async def enter_step(self, step: str) -> None:
162
+ # The HMI's STEPS in station.ts lists the same names, in this order.
163
+ self.step = step
164
+ await publish_status()
165
+
154
166
  async def run_cycles(self) -> None:
155
167
  recipe = self.recipe
156
168
  if recipe is None:
@@ -159,18 +171,21 @@ class IndexingStation(StateMachine):
159
171
  for cycle in range(1, recipe.cycle_count + 1):
160
172
  if self.stop_requested:
161
173
  break
162
- started = time.monotonic()
163
174
  self.cycle = cycle
164
- await publish_status()
175
+ self.cycle_started = time.monotonic()
176
+ await self.enter_step("Index")
165
177
  await asyncio.to_thread(self.index_part, recipe)
166
178
  if self.stop_requested:
167
179
  break
180
+ await self.enter_step("Clamp")
168
181
  await asyncio.to_thread(self.clamp_part)
169
182
  if self.stop_requested:
170
183
  break
184
+ await self.enter_step("Dwell")
171
185
  await asyncio.sleep(recipe.dwell_ms / 1000)
186
+ await self.enter_step("Release")
172
187
  await asyncio.to_thread(self.release)
173
- self.last_cycle_seconds = time.monotonic() - started
188
+ self.last_cycle_seconds = time.monotonic() - self.cycle_started
174
189
  await publish_status()
175
190
  await publish_event("info", f"Cycle {cycle}/{recipe.cycle_count} done", f"Took {self.last_cycle_seconds:.1f} s.")
176
191
  await asyncio.to_thread(self.release)
@@ -3,7 +3,7 @@ name: vention-monitoring
3
3
  description: Diagnose deployed Vention machines from controller state, history, health, and activity data. Use when the user reports a machine that is down, faulted, offline, slow, or underperforming, asks why a cycle time regressed, wants to interpret MachineMotion event data or drive and brake status, asks about OEE or uptime, or wants to know which machine in a fleet needs attention. Trigger on a MachineMotion serial number plus any complaint.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.9.16"
6
+ version: "0.9.19"
7
7
  author: Vention
8
8
  ---
9
9