Skip to content

MCP Server - Do Not Use This Software!! - #2614

Draft
tyeth wants to merge 493 commits into
Snapmaker:mainfrom
tyeth:startup/base
Draft

tyeth wants to merge 493 commits into
Snapmaker:mainfrom
tyeth:startup/base

Conversation

@tyeth

@tyeth tyeth commented Sep 7, 2026 •

Copy link
Copy Markdown

No warranty expressed or implied. Use at your own risk. Ensure the safety of yourself and others and property at all times, following local laws and safety requirements, use Personal Protective Equipment, and an Enclosure for the CNC.
You must always be next to the machine ready to perform an emergency stop or switch the power off.

@tyeth tyeth changed the title MCP Server MCP Server - Do Not Use This Software!! Sep 7, 2026
tyeth and others added 29 commits September 29, 2026 20:22
…uency; the idle baseline follows the spindle up

Dry run 9a2697a71a2f (2026-09-29): the spindle at S8000 sits on a soft-start
plateau of ~7480 RPM (125 Hz) for its first ~15-20 s, then holds 8000
loaded or not - so "loaded vs unloaded" was wrong, it was time since M3.
The X rapid also sings a 125 Hz series, so a rapid with the spindle
commanded on read as 7480 RPM.

- During a RAPID the gantry comb is also fitted unmasked; a spindle
  candidate on one of its harmonics (either way round, within 2 %) is
  `ambiguous`: recorded, never RPM. Feed moves are left alone - while
  cutting the spindle IS the strongest comb - and rapids carry no load.
- The idle baseline is the median of the most recent 3 s of settled free
  frames, so the plateau is replaced by the settled speed before a cut is
  judged, and a cut that starts too soon after M3 shows the plateau as a
  reach error rather than hiding it.
- Synthetic free phase is a still dwell by default (`freeKind`), with a
  `gantryFree` tone option; tests for the ambiguous rapid and the rolling
  baseline across the plateau.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… sags are judged against the idle baseline

Job beb4bf0a3d22 (2026-09-29): the panel's RPM stopped two passes before
the cut ended. The timing model treated every G0 as a 3000 mm/min rapid,
but on the A350's CNC firmware a G0 travels at the modal F once one has
been set (81 mm returns took ~9.7 s = F500), so the model ran ahead, hit
the queued line - which is 16 blocks AHEAD of the head - and was capped
there; the spindle context reached M5 while the head still had passes to
run, and the tracker stopped.

- executionModel: G0 duration uses the modal feed when known; the head
  is bounded to the block `lead` behind the queue (no further back than the
  blocks queued since the last sync) plus one block of slop, and may only
  close the whole gap when the queue is frozen at a sync or the file's end.
- spindleTracker: sags are judged against the rolling idle baseline (the
  operator's rule and the reference script); the best sustained loaded
  median is reported only - a single high mis-lock cluster made it 8280 on
  this job and every honest 8000 read as a 3.4 % sag. Tests updated to the
  rule: a spindle that idles low and cuts at S is HOLD with a reach flag,
  one that idles at S and drops 8 % under load is STRUGGLE.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… real head

Job b3e8f3dd0e34 (2026-09-29 22:08): the operator stopped the file 5.9 s in,
the spindle had never turned, the head was 55 mm into its Y traverse - and
the tracker reported a 7472 RPM lock plus a "not reaching" event. Three
defects, all fixed from the recordings of that job and of d8006b17b237:

- ExecutionEstimator.setStartPosition() was never called by the session:
  the leading G0s were timed at 0 s and the model sat on the dwell after M3
  from t = 0. The session now takes the head's pre-job position from the
  last heartbeat, and the modal feed the controller still holds from the
  previous file (the A350 runs a file's leading G0s at it: a 106 mm Z
  descent took 12.7 s at the inherited F500, not 2.1 s).
- inferExecutingLine() walked onto the comment after the 20 s dwell, so the
  frozen queue looked live and syncFollows() said no; it now stops at the
  last sync line of a non-motion run, and a head reported at a sync can be
  re-matched to the segment it just finished.
- There is no spindle "soft-start plateau": the 124.5 Hz comb read as
  7477 RPM during every post-M3 dwell was the Z axis descending at F500.
  Its second harmonic is 15-17 dB above its fundamental, while the
  spindle's fundamental is always its strongest low harmonic (idle 8010:
  1x +29 / 2x +23 dB; cutting 8000: +26 / +15). A candidate whose 2x is
  >= 10 dB over its 1x (5-frame median) is `gantryLike` and never a lock,
  whatever the estimated context says. Replayed: 0 locks on the level 6
  traverse and on level 4's Z descent; every frame locked on the real
  spindle from 2 s after M3.

Also: a `flags` column in the audio ring (locked / clipped / contested /
ambiguous / gantry-like), the analyser's `latestSpectrum()` (96 log bins,
30 Hz-8 kHz) in live.json, and the camera panel's octave-band bars - which
saturated whenever the cut or the gantry got loud - replaced by a scrolling
spectrogram with the spindle 1x / 4x and the gantry tone marked.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Eval 17 in the cnc-motion-rules suite: plan a safe bed and workpiece
survey on the PR #219 build with the touch probe fitted (~75 mm),
camera alignment at safe heights, a coarse survey, then a fine survey
of the enclosure's tailstock end for a later screw hole.

The workspace records iteration 1 at bbef661: fixtures modelled on
the box on 2026-10-04 (not homed, B180, no active tool, no camera
model), the live tools/list, four dry-run plans and the grading.
opus 17/20, sonnet 15/20, haiku 4/20, sonnet without skills 11/20.
Every model missed that an active tool lets camera routes run below
the Z320 floor against stored landmarks only - it is undocumented.

Force-added under the ignored .claude/skill-evals/ like the other
eval workspaces; scheduled for removal in the 2027 purge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A self-contained brief for the agent that will update the CNC skills
and MCP docs after iteration 1 of the bed survey eval: the results,
thirteen PR #219 behaviours verified in the source (the active tool
overriding every protrusion source and opening sub-floor camera
routes, camera_program's op shapes, fit_calibration storing the 2x2
matrix rather than a camera model, unhomed refusals, where bootstrap
frames live), gaps G1-G20, proposed skill and doc wording, product
changes for the operator to decide, and how to run iteration 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add camera-operation guidance, exact composite schemas, remote capture/index
retrieval, persistent requested snapshots, and independent bootstrap holdouts.
A safe enumerated programme uses one approval, including its initial Z move;
bootstrap now establishes its advertised park height before taking any XY leg.

Keep the motion floor independent of fitted-tool assertions, scope clearance
exceptions to individual operations, recheck camera envelopes, preserve partial
evidence on failure, and invalidate stale fitted-tool authority. Explain probe
conversion versus clearance, legacy boxes, physical planes, future-cut datums,
probe reach and event budgets. Rebuild distributable skill archives.

Update future eval assertions without running agent evals. Do not infer full
rotary obstacle volumes from axis points, physical plane corrections from seam
drift, or calibration validity from an inverse-matrix check.
A CircuitPython/Blinka-compatible monitor (vibration_monitor.py) streams
LSM6DSOX / LSM6DSO32 / ISM330DHCX tagged-FIFO samples at the chip's ODR,
or polls any Adafruit-driver IMU, behind optional TCA9548A muxes. The pure
Node side decodes its batches, re-measures the real sample rate, and
analyses each sensor: Welch PSD, levels (g, ISO 10816 velocity,
displacement), tones with direction, a harmonic comb for spindle RPM and
the non-harmonic (chatter) tones, spatial periods for a moving axis,
activity segmentation and a position profile, tilt against a baseline,
and the rotary chuck's absolute angle from a fitted gravity calibration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The feed runs the monitor over its own Blinka bridge or a CircuitPython
board on serial, refusing the probe feed's USB bridge. Captures record
windows (with look-back), keep raw float32 samples on disk for
re-analysis, and optionally record every file job. Seven read-only tools:
get_vibration_status, capture_vibration, get_vibration_capture,
stop_vibration_capture, list_vibration_captures, measure_rotary_angle and
calibrate_rotary_accelerometer. Settings -> MCP Server gains an
Accelerometers section; get_stored_state and job status report it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
README section (hardware, bridges, configuration, what the analysis can
and cannot say, files and timing), per-tool reference, optional poll-mode
driver requirements, and a cnc-vibration skill with recipes for axis
noise, toolhead deflection, tailstock chatter and rotary absolute B. CI
runs the monitor's simulated-sensor Python tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd feed

RPM comb: refine from the line within +-2 bins (a lone clean line read up
to 1.5 bins low) and count every harmonic's weight (a 2-flute cut locked an
octave high); half-rate ambiguity is a real line at f0/2. Envelope time is
the whole-sample window. Captures no longer drop live samples as look-back
duplicates, mark every new monitor run as a gap and map windows through a
time index, re-analyse in yielding chunks, write meta at start and read the
controller's B raw instead of through the position-of-record judge. A
rotary reading is still only with 3 whole seconds, low time-domain
vibration, no gravity drift and a bias-free gyro; calibration needs 4
readings over 180 deg. The feed allocates rings lazily, closes a serial
port that finishes opening after close, strips inherited BLINKA_* and
refuses the running probe feed's bridge; settings restart it only on a
real change. Configurations the bus or memory cannot carry are refused;
the monitor flushes before an overrun and pairs accel/gyro counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PR review: a reconnect is now a gap (per-sensor streams outlive their
monitor in the pure vibrationStream.ts, and a settings change ends the
captures recording them). Every poll sensor goes through the mux-aware
adapter, a root sensor may not share an address with a muxed one, and a
corrupt FIFO word ends the batch and marks the next as a gap. Poll gyro is
recorded. The bridge check becomes explicit resource leases (USB bridge,
native I2C pins, serial ports) against the stored and running probe feed,
with exactly one bridge selector. Several CircuitPython boards run as
separate links. Raw samples are written asynchronously with backpressure,
a global active budget and a disk budget. Tests: eight interleaved
streams through a restart, eight sensors over four mux channels, root
versus open-channel isolation, bad-tag gaps, multi-board and lease rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page sent its keepalive only at the end of a full status poll, and every
poll enumerated serial ports. With the 500 ms poll and skipped overlapping
polls, a slow enumeration left about a second between keepalives and the
server disarmed with the one-second heartbeat message, often mid-jog.

- Send the keepalive from its own 200 ms timer while armed and visible.
- Cache the serial port list for status polls (2 s); connect still lists fresh.
- Name the failing field in rejected frames, e.g. feed 60 outside 300-3000,
  and log the rejected line once per new error.
- Firmware resets feed to the imported SLOW_FEED rather than a literal, sends
  its build id (logged on connect) and rare link/write events.
- LUBAN_PENDANT_TRACE=1 logs every USB line in both directions for debugging.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of the decoupled keepalive found it could keep a session armed while
the page itself was frozen.

- Stop keepalives once status has not rendered for 1.5 s; a failed poll
  clears the armed flag, and polls started before an arm cannot clear it.
- Use AbortController with a timer instead of AbortSignal.timeout, which
  Electron 15 (Chromium 94) lacks.
- Status polls wait at most 1 s for a port enumeration and otherwise reuse
  the last list; the cache is stamped when enumeration finishes.
- Keep live values out of rejected-frame reasons so a bad stream logs one
  line rather than one per frame; truncate firmware events to 120 chars.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator rules for the feed (mm/min):
- 300 when the Feather boots; nothing else resets it.
- Twist in feed mode adjusts it within 60-3000 (minimum lowered from 300).
- Entering Z mode caps it at 1000; a lower feed is kept, and returning to
  feed mode keeps the capped value. Arm, disarm, STOP and USB loss no longer
  reset it (they still require neutral before the next jog).
- The host rejects Z-mode frames above 1000 as well as anything outside
  60-3000, naming the limit.

Firmware id pendant-2026-10-05b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pendant page now draws a plan view of the bed in machine X/Y (Y up):
known travel, the reviewed or armed envelope, each obstacle exclusion
labelled with its required Z, and the toolhead (live dot, or a held ring
when the DRO is not verified). A table lists each exclusion with the
stored height, basis and notes. Obstacles the envelope reaches below
their required Z turn red, with a warning beside Arm naming the obstacle,
its footprint and required Z.

An obstacle refusal no longer disarms. The offending segment is never
sent; Luban stays armed and sends only motion that stays clear: the
approach up to 0.5 mm outside the exclusion, or the stick intent with
the offending axis component removed. Every transmitted segment still
passes the complete obstacle check. The hold is reported as `blocked`
in /pendant/status and the USB DRO frame, mirrored beside the map, and
shown on the Feather TFT bottom row while linked (red BLOCKED, amber
LIMITED, scrolling past 38 characters). It clears when the stick is
centred or a full segment is accepted. The first hold per obstacle per
arm is logged. There is no landmark override.

A pre-fw Feather is now logged once per connection as unidentified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…idence

A failed tool call could leave the controller in G53 or G91. Every call
now records its direct sends on a per-call modal ledger. When a call
fails and may have left a non-preferred mode, the registry sends G90 and
G54 as separate, axis-free commands, but only while the call still owns
the machine, on the same connection generation, with no other operation,
job or pendant active, the machine idle, and no stop, trip or crash
alarm latched. The original error is preserved and a failure_recovery
block reports attempts, skips, outcomes, connection identity, verified
or unverified modes and position trust, pointing at restore_work_frame
whenever recovery is incomplete. Nothing is retried, and inert tests
cover the scenarios in the issue.

Closes #221

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator decision: a segment with X and Y unchanged (within
POSITION_EPSILON_MM) and Z strictly rising is permitted regardless of
obstacle boxes, in the final validateJogSegment check itself; the
envelope and travel Z limits still apply. Arming inside an exclusion
below its required Z is now allowed so the pendant can climb out. Until
it leaves, `blocked` reports `inside` with "INSIDE <name> below Z<n>:
Z-up only" (page, DRO frame and TFT, shown while linked even with the
stick centred), and every other motion holds, including Z-up with any
XY component.

Review fixes for #227:
- The approach pad (0.5 mm XY, 0.1 mm Z) now applies to the first hit
  test and the axis-drop candidates, per box whose padded volume does
  not already contain the start, so short segments stop at the pad
  instead of creeping to the exclusion edge. Only validateJogSegment
  uses the exact test.
- Targets are rounded to the 3 decimals moveMachineSettled sends, kept
  inside the armed envelope, before any check, so the validated numbers
  are the transmitted ones.
- Displayed heights round required Z up and requested Z down to 0.01 mm
  (runtime text, page map and warning, TFT).
- An axis drop never removes the dominant stick axis, so a Z-down twist
  with slight XY drift holds instead of sending only the drift.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tyeth and others added 30 commits October 19, 2026 16:24
…type check

The no-text result now carries the per-query coverage of #315
(what SACP gives instead, and what has no SACP equivalent). The text
route's type no longer relies on strict null narrowing (tsconfig-server).

Refs #265 #312 #315

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ool text

Refs #312

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… firmware view

RayController is the only firmware whose 0x01 0x21 trailing SN layout is
known (#315), so the Ready-time adoption is limited to the Ray, and the
identity block in the firmware view to GRBL dialects: an A350 whose UI
read its machine info still reports exactly what it did before.

Refs #265 #312 #315

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…th M5

Ray firmware V1.7.4 never reports laser power (0x12 0xa1 / 0x12 0x01 are
always 0, #315 mismatch 2) and nothing sets headStatus, so every
toolhead-on guard passed on the Ray whatever the laser was doing.

- gcodeDialect: a ToolheadReadback per row; the Ray's is 'unknown' with
  M5 as its laser-off line, every other row 'reported'.
- toolheadState.ts (pure): one assessment feeds every guard (camera move
  and home, move_z, traverse_xy, laser tools, probing, tool setter,
  workspace, pendant). Unknown is reported, never read as off; a
  non-zero reading still refuses.
- gcodeForms.relativeStep leads every Ray step with M5 (laserPlan opts
  out, it manages its own M5/M3/M5); home sends M5 as its own payload
  before the unchanged $H.
- An advisory MCP-sent-only laser record (M3/M4 with S > 0 and no M5
  after), in get_position, get_machine_profile and motion results on the
  Ray only. A350 and Original output is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
start_gcode_job's direct-step result now carries the toolhead block with
laser_off_sent on the Ray (and a laser_off_sent job event), so the staged
traverse_xy, goto_work_origin and laser bodies report the M5 they sent,
as move_and_capture and home already do. Tests pin goto_work_origin's
M5 lead and that the validator's verdict on a Ray traverse is unchanged
by it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix: Run the first-connect read and report the Ray's SACP reply limits

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix: Treat the Ray's toolhead state as unknown and lead its motion with M5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
start_gcode_job required file upload and start for every job kind before
branching on it, so every approved direct job on the Ray (SACP over UDP)
and the Original (text serial) was refused with "does not support file
job submission" (live 2026-10-19, jobs 36fa11c67b4e and 7a19f87b186a).
The check is now per kind, still before the token is consumed: a file
job needs upload and start, a direct job or procedure needs executeGcode.
stop_gcode_job requires only the channel's stop. The A350 channel has
both capabilities, so its path is unchanged.

goto_work_origin's staging result now carries confirm_url and next_step
like traverse_xy, and a source-level test checks every staging site.

Closes #318
Refs #265

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #318

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix: Start direct jobs on channels without file upload and return goto_work_origin's confirm link

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In Luban's Manual Mode (SACP 0x01 0x48 motor power SHUTAll) the Ray V1.7.4
ACKed $H with result 0 and never moved; MCP home timed out after 120 s
(live 2026-10-19). Control Mode (STAYPOWER) homed it in about 4 s.

- gcodeDialect: a motorPower readback per row; only ray-grbl reads one
  (sacp-0x48), every other row is 'none', so the A350, the Original, the
  Artisan and the J1 send no 0x48 request and are not gated.
- motorPower.ts (pure): a bounded (3 s) GETCURRENTMODE read that never
  throws, the mode decode and the refusal. Only STAYPOWER passes:
  RayController 91a675f runs SHUTAll and ONLYLINEAR through the same code
  (motion_platform.cpp:461-472) and the steppers only treat STAYPOWER
  differently (Stepper.cpp:474). Unknown refuses ("motor power unknown").
- SacpChannelBase.getMotorPowerMode: read-only accessor (GETCURRENTMODE),
  so the MCP never holds a setter. The MCP never switches the mode.
- Gates, read fresh each time: home before anything is sent (M5 included),
  start_gcode_job and policy-approved transport before the approval is
  consumed, and move_and_capture before it sends.
- get_position and get_machine_profile carry motor_power on the Ray.

Closes #321
Refs #265 #315 #319

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…to Control Mode

Luban homes the Ray when Set Origin Mode changes to Control Mode, and a
Ray direct step is a relative increment planned from the position before
that, so starting the refused job afterwards would not reach the
approved destination. The refusal, the start_gcode_job text and
TOOLS.md now say to withdraw it with stop_gcode_job and restage; the
approval is still left unconsumed.

Refs #321

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix: Refuse Ray home and job starts while its motors are off

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A direct job records the judged machine position at staging. On a
relative-steps machine (Ray, Original, J1) start_gcode_job, and every later
step of a batch, refuses before the token is consumed when the position
differs by more than POSITION_EPSILON_MM from where the step was planned
from (staging, then the previous step's settle line), or the machine is
unhomed or its position unreliable. The A350's absolute G53 steps are not
checked. Closes #323.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
stop_gcode_job (and the dashboard's Stop) on a direct batch with no step
in flight now withdraws it: the token is cleared, the job ends withdrawn
and nothing is sent. A running step keeps the firmware stop where the
channel has one; over SACP-UDP and text serial the remaining steps are
withdrawn, the running step finishes, and the result says a running step
cannot be stopped by firmware command over that transport. A job a
firmware stop ended mid-step is no longer re-armed when the step returns.
Closes #320.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A withdrawn or ended direct job reached the head-position check before consumeToken and was refused as 'the head moved since staging' when the head had moved. It now gets the token's own refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oval-free settings

set_motor_power {mode, reason} (Ray only) stages a confirm page: control
sets STAYPOWER over SACP 0x01 0x48 and then homes with M5 and $H, as
Luban's Control Mode does; manual sends M5, sets SHUTAll and latches the
position unhomed until a home. The channel setter is reached only from
this tool's approved job or the operator-only setting.

Two per-machine settings on the Motion rules pane, default off and never
writable through MCP: "Allow moves without an approval page" (traverse_xy,
move_z, goto_work_origin, goto_tool_change_position, a laser-off
laser_frame) and "Allow motor-mode changes without an approval page".
With one on, the action runs on the call only with the agent's quote of
the operator's instruction, recorded on the result and the job; every
other gate still applies. Both are reported in applied_records. Closes #326.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also say on the Motion rules pane that relative-step machines stage every move unless the new setting is on, and name home as the way out of the released-motors latch after a switch in Luban.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix: Re-check the head position before starting relative-step direct jobs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix: Withdraw direct batches between steps instead of sending a firmware stop

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Feature: Switch the Ray's motor-power mode and add operator settings for approval-free moves

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nels at staging

On the Snapmaker Ray (V1.7.4, SACP over UDP) submit_gcode_job staged and the
operator approved a laser file job, and start_gcode_job then refused it with
"The connected channel does not support file job submission" (job 8595d76f9e33):
SacpUdpChannel had neither uploadGcodeFile nor startGcodeJob.

Luban's own Ray UI (RayUploadWidget) uploads the job by the compressed SACP
transfer as ray.nc and tells the operator to press and hold the work switch;
the firmware (RayController file_manager.cpp) has NO start command for an
uploaded file - hmi_req_cb_start_file_print ACKs "need use press button" - and
only starts /ray.nc from a long press. So the MCP now does exactly that:

- SacpUdpChannel: uploadGcodeFile (compressUploadFile to ray.nc with a 30 s
  inactivity watchdog; a lost result is `uncertain`, a refusal `rejected`),
  stopGcodeJob (the SACP job stop 0xAC 0x06 Luban's Stop sends) and
  fileJobTransport() = operator-switch start, file-print-only stop.
- SacpChannel records the 0xAC 0x1A/0xA0 job progress into the machine state
  as gcodePrintingInfo (SSTP's shape), and re-reads the file info on the
  idle -> running edge of the legacy heartbeat.
- gcode.ts: a new `armed` job state after the upload (nothing sent, holds the
  machine); the heartbeat watcher adopts the run for ray.nc (SYSTEM_STATUS_
  STARTING counts as active, /ray.nc == ray.nc), progress and completion as
  on the A350; a lost upload result is start_unverified (never retried);
  stop withdraws an armed job (ray.nc stays on the controller - said so),
  sends 0xAC 0x06 to a running one, and sends nothing to an idle Ray (that
  stop alarms outside a file print) - so #320's "no firmware stop for a
  direct step" semantics hold although the channel now has a stop.
- submit_gcode_job REFUSES at staging on a channel that cannot start file
  jobs (Ray over USB, Original, Artisan/J1 over Wi-Fi), naming the channel
  and machine, so no approval is ever asked for a job that cannot run; the
  confirm page carries a STARTED AT THE MACHINE banner on the Ray.
- Dashboard: armed jobs listed under running with their own label.
- The A350's SstpHttpChannel is untouched.

Tests: fake SACP client (sacpConnection.test.ts) for the upload target,
rejection, silence watchdog, stop and printing info; fileJobStart.test.ts for
the staging refusal, armed -> started -> completed, stop while armed/running/
idle, uncertain upload, Manual Mode refusal, direct-step stop; jobEnding for
the armed stop plan.

Fixes #328

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix: Run MCP file jobs on the Ray over SACP (upload ray.nc, operator-switch start) and refuse incapable channels at staging
…metry tools

The release tooling job failed for v4.15.2-mcp-alpha.9 ("No category for
tools/enclosure.ts"), leaving the alpha.8-era 65-tool list in the notes.

- Categorise enclosure, motorPower, laser, telemetry and vibration tool files
- Scan cameraProgram.ts (camera_program is registered outside tools/)
- Resolve `name: TOOL` constant names (set_motor_power)
- Escape the e.g./i.e. lookbehinds: "machine." never ended a sentence
- Never cut a summary inside an open parenthesis
- Read and write UTF-8 explicitly so local Windows runs match CI
- TOOLS.md: add get_job_telemetry and list_audio_devices

The list now covers all 93 registered tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix: Generate the MCP tool list for every tool file and document telemetry tools
Clicking a machine or work coordinate on the Workspace page turns it into
an input pre-filled with the current value. Enter (or clicking away) sends
an absolute move on that axis only; Escape cancels. Clicking in and out, or
retyping the shown value, sends nothing.

Both columns move through the same work-frame G0 the axis dropdown's
"Go To Work Zero" uses: a machine-column value is converted with the
displayed origin offset (work = machine + offset), so the move lands where
the table will then show the typed value, on every machine, without a G53
window. The Ray refuses targets outside its work area, as the jog pad does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Ray's GRBL firmware runs G0 at each axis's maximum rate and ignores
F, so a position typed into the coordinate table crossed X far faster
than the jog speed. Send G1 with the jog speed there, as the jog pad
already does; other machines keep G0, whose F their firmware honours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Feature: Type a position into the Workspace coordinate table to go there
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant