mcp-3d-printer-server 1.2.28 → 1.2.29

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.29] – 2026-11-04
4
+
5
+ ### Changed
6
+
7
+ - With no method given, a FULU print over Bambu Cloud goes the way Bambu Studio sends it to a cloud-bound printer it can reach on the LAN (`PrintJob.cpp`): `start_local_print_with_record`, which uploads to the printer over the LAN and records the task in the account, in the account's cloud session. When that fails before the print command (the plug-in's `-2010` to `-2140` preparation results, other than a missing or oversized file), the same checked job goes once through `start_print`, with the printer checked again first. Any other failure may have reached the printer and is not sent again. A printer that reports no storage goes straight to `start_print`, as in Studio. Same change as bambu-printer-mcp 1.1.55. On a P1S with firmware 01.10.00.00, `start_print` alone returns `-3120`, HTTP 403 "internal blocking", every time. Errors name `-2120`, `-2130` and `-3120`. Checked with the stand-in host and intercepted transports only; not yet on a printer.
8
+
3
9
  ## [1.2.28] – 2026-11-04
4
10
 
5
11
  ### Added
@@ -186,10 +186,16 @@ export declare class BambuNetworkBridge {
186
186
  * Shares the LAN session queue: one reader of the plug-in's events at a time.
187
187
  */
188
188
  withCloudSession<T>(devId: string, options: BambuNetworkBridgeOptions, assertActive: () => void, timing: Readonly<CloudTiming>, run: (session: CloudSession) => Promise<T>): Promise<T>;
189
- /** A cloud print method (start_print) in a cloud session, so the plug-in has the printer's state. */
190
- startCloudPrint(devId: string, method: string, payload: JsonObject, options?: BambuNetworkBridgeOptions, assertActive?: () => void, timing?: Readonly<CloudTiming>): Promise<{
189
+ /**
190
+ * A cloud print in a cloud session, so the plug-in has the printer's state. With a fallback this is
191
+ * Bambu Studio's order for a cloud-bound printer it can also reach on the LAN (PrintJob.cpp,
192
+ * SendAttemptRoute::LanWithRecord, then ::Cloud): start_local_print_with_record, then start_print
193
+ * when the first failed before its print command was sent. `firstFailure` is the first method's result.
194
+ */
195
+ startCloudPrint(devId: string, method: string, payload: JsonObject, options?: BambuNetworkBridgeOptions, assertActive?: () => void, timing?: Readonly<CloudTiming>, fallbackMethod?: string): Promise<{
191
196
  result: unknown;
192
197
  jobUpdates: BridgeJobUpdate[];
198
+ firstFailure?: number;
193
199
  }>;
194
200
  /**
195
201
  * A print control over Bambu Cloud: how Bambu Studio and FULU's slicer control a printer
@@ -21,8 +21,16 @@ const DEFAULT_REQUEST_TIMEOUT_MS = 120000;
21
21
  const LOCAL_CONNECT_STATUS = ["ok", "failed", "lost"];
22
22
  // bambu_networking.hpp BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED.
23
23
  const LOCAL_PRINT_PUBLISH_FAILED = -4030;
24
+ // A with-record send falls back to start_print only on its own preparation failures (the plug-in's
25
+ // PRINT_WR_* results, -2010 to -2140: project, upload, task record, FTPS), which come before the print
26
+ // command, so the printer has no job. Any other result may have reached the printer and is never
27
+ // replayed. Of those, Studio also stops on a file missing or too large (should_stop_without_fallback).
28
+ const WITH_RECORD_PREPARATION_FAILURE = (value) => value <= -2010 && value >= -2140 && value !== -2040 && value !== -2100;
24
29
  // bambu_networking.hpp print errors that name a recoverable cause.
25
30
  const BAMBU_NETWORK_PRINT_ERRORS = {
31
+ [-2120]: "Bambu Cloud refused to create the print task",
32
+ [-2130]: "FTPS upload to the printer failed",
33
+ [-3120]: "Bambu Cloud refused to create the print task",
26
34
  [-4010]: "the file exceeds the 1 GB upload limit",
27
35
  [-4020]: "FTPS upload to the printer failed",
28
36
  [LOCAL_PRINT_PUBLISH_FAILED]: "the networking plug-in could not send the print command to the printer over MQTT",
@@ -703,9 +711,23 @@ export class BambuNetworkBridge {
703
711
  release();
704
712
  }
705
713
  }
706
- /** A cloud print method (start_print) in a cloud session, so the plug-in has the printer's state. */
707
- async startCloudPrint(devId, method, payload, options = {}, assertActive = () => { }, timing = CLOUD_PRINT_TIMING) {
708
- return await this.withCloudSession(devId, options, assertActive, timing, async (session) => ({ result: await this.callWithAgent(method, payload, options), jobUpdates: session.jobUpdates }));
714
+ /**
715
+ * A cloud print in a cloud session, so the plug-in has the printer's state. With a fallback this is
716
+ * Bambu Studio's order for a cloud-bound printer it can also reach on the LAN (PrintJob.cpp,
717
+ * SendAttemptRoute::LanWithRecord, then ::Cloud): start_local_print_with_record, then start_print
718
+ * when the first failed before its print command was sent. `firstFailure` is the first method's result.
719
+ */
720
+ async startCloudPrint(devId, method, payload, options = {}, assertActive = () => { }, timing = CLOUD_PRINT_TIMING, fallbackMethod) {
721
+ return await this.withCloudSession(devId, options, assertActive, timing, async (session) => {
722
+ const result = await this.callWithAgent(method, payload, options);
723
+ const value = bridgeNumericResult(result);
724
+ if (!fallbackMethod || value === undefined || !WITH_RECORD_PREPARATION_FAILURE(value))
725
+ return { result, jobUpdates: session.jobUpdates };
726
+ options.trace?.({ step: "fallback", from: method, to: fallbackMethod, result: value });
727
+ const params = payload.params;
728
+ const fallbackPayload = params ? { ...payload, params: { ...params, comments: `failed(${value})` } } : payload;
729
+ return { result: await this.callWithAgent(fallbackMethod, fallbackPayload, options), jobUpdates: session.jobUpdates, firstFailure: value };
730
+ });
709
731
  }
710
732
  /**
711
733
  * A print control over Bambu Cloud: how Bambu Studio and FULU's slicer control a printer
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ import { BambuNetworkBridge, describeBambuNetworkPrintError } from "./bambu-netw
34
34
  import { PRINT_ROUTES, connectionForRoute, routeOf, routeOptions, writeEnvValues } from "./counterpart/route.js";
35
35
  import { resolveStudioPrintOptions, usedNozzleCount } from "./studio-print-options.js";
36
36
  import { inspectPrintFile } from "./safety/print-file.js";
37
- import { validatePrinterState } from "./safety/printer-state.js";
37
+ import { externalStorageState, validatePrinterState } from "./safety/printer-state.js";
38
38
  import { uniquePrintName, withPrinterOperation, withPrintSnapshot } from "./safety/artifact.js";
39
39
  import { checkedFile } from "./printers/bambu.js";
40
40
  import { CONFIRMATION_UNAVAILABLE, confirmationTimedOut, confirmationTimeoutMs } from "./safety/confirmation.js";
@@ -881,8 +881,10 @@ class ThreeDPrinterMCPServer {
881
881
  throw new Error("FULU BambuNetwork printing requires bambu_serial, the printer host and its LAN access code: fresh printer state is checked over MQTT before every bridge print, including cloud dispatch.");
882
882
  }
883
883
  const isLocalBridgePrint = connectionType === "lan";
884
- const bridgePrintMethod = isLocalBridgePrint ? "start_local_print" : "start_print";
885
- const bridgeMethod = `net.${bridgePrintMethod}`;
884
+ // Bambu Studio sends a cloud-bound printer it can reach on the LAN with record first, in the cloud
885
+ // session, then start_print (PrintJob.cpp). This server always has the printer's IP and access code.
886
+ let bridgeMethod = isLocalBridgePrint ? "net.start_local_print" : "net.start_local_print_with_record";
887
+ let cloudFallbackMethod = isLocalBridgePrint ? undefined : "net.start_print";
886
888
  const { plateIndex } = options;
887
889
  const threeMfFilename = path.basename(options.threeMFPath);
888
890
  const projectName = threeMfFilename.replace(/\.3mf$/i, "");
@@ -923,6 +925,11 @@ class ThreeDPrinterMCPServer {
923
925
  validatePrinterState(await bambu.getSafetyStatus(devIp, devId, password), requirements);
924
926
  const confirmedStatus = await bambu.getSafetyStatus(devIp, devId, password);
925
927
  validatePrinterState(confirmedStatus, requirements);
928
+ if (cloudFallbackMethod && externalStorageState(confirmedStatus.raw) !== "normal") {
929
+ // Studio sends straight through the cloud when the printer has no storage for the LAN upload.
930
+ bridgeMethod = cloudFallbackMethod;
931
+ cloudFallbackMethod = undefined;
932
+ }
926
933
  const wireMapping = mapping.map((value) => bridgeAmsMappingValue(value, dualNozzle));
927
934
  const remoteFileName = uniquePrintName(options.threeMFPath);
928
935
  const checkedName = remoteFileName.replace(/(?:\.gcode)?\.3mf$/i, "");
@@ -979,23 +986,29 @@ class ThreeDPrinterMCPServer {
979
986
  const bridgeOptions = {
980
987
  // Trusted in-process preflight immediately before the print frame.
981
988
  beforeDispatch: async (method) => {
982
- if (method !== bridgeMethod)
989
+ if (method !== bridgeMethod && method !== cloudFallbackMethod)
983
990
  return;
984
991
  assertActive();
985
992
  validatePrinterState(await bambu.getSafetyStatus(devIp, devId, password), requirements);
986
993
  },
987
- assertDispatchAllowed: (method) => { if (method === bridgeMethod)
994
+ assertDispatchAllowed: (method) => { if (method === bridgeMethod || method === cloudFallbackMethod)
988
995
  assertActive(); },
989
- onDispatched: (method) => { if (method === bridgeMethod)
996
+ onDispatched: (method) => { if (method === bridgeMethod || method === cloudFallbackMethod)
990
997
  retainForDispatchedUpload(); },
991
998
  };
992
999
  let jobUpdates = [];
993
1000
  let bridgeResult;
1001
+ let firstAttempt = "";
994
1002
  if (isLocalBridgePrint) {
995
1003
  ({ result: bridgeResult, jobUpdates } = await this.bambuNetwork.startLocalPrint({ devId, devIp, username: "bblp", password, useSsl: true }, bridgeMethod, { client_job_id: clientJobId, params }, bridgeOptions, assertActive));
996
1004
  }
997
1005
  else {
998
- ({ result: bridgeResult, jobUpdates } = await this.bambuNetwork.startCloudPrint(devId, bridgeMethod, { client_job_id: clientJobId, params }, bridgeOptions, assertActive));
1006
+ let firstFailure;
1007
+ ({ result: bridgeResult, jobUpdates, firstFailure } = await this.bambuNetwork.startCloudPrint(devId, bridgeMethod, { client_job_id: clientJobId, params }, bridgeOptions, assertActive, undefined, cloudFallbackMethod));
1008
+ if (firstFailure !== undefined) {
1009
+ bridgeMethod = cloudFallbackMethod;
1010
+ firstAttempt = ` It first tried net.start_local_print_with_record, as Bambu Studio does, which returned ${firstFailure}${describeBambuNetworkPrintError(firstFailure)}.`;
1011
+ }
999
1012
  }
1000
1013
  if (typeof bridgeResult === "object" && bridgeResult !== null && bridgeResult.ok === false) {
1001
1014
  throw new Error(`FULU BambuNetwork bridge method ${bridgeMethod} failed: ${String(bridgeResult.error || "unknown bridge error")}`);
@@ -1005,14 +1018,14 @@ class ThreeDPrinterMCPServer {
1005
1018
  const reported = jobUpdates.filter((update) => update.code !== 0 || update.msg).slice(-3)
1006
1019
  .map((update) => `status ${update.status}, code ${update.code}${update.msg ? `: ${update.msg}` : ""}`).join("; ");
1007
1020
  throw new Error(`FULU BambuNetwork bridge method ${bridgeMethod} returned non-zero result ${value}${describeBambuNetworkPrintError(value)}.` +
1008
- (reported ? ` Plug-in reported: ${reported}.` : ""));
1021
+ (reported ? ` Plug-in reported: ${reported}.` : "") + firstAttempt);
1009
1022
  }
1010
1023
  assertActive();
1011
1024
  // Bambu's plug-in placed the file, so a resume re-reads it if it can and otherwise asks a person.
1012
1025
  bambu.recordCheckedJob(devIp, devId, remoteFileName, requirements, resumeFile, false);
1013
1026
  return {
1014
1027
  status: "success",
1015
- message: `FULU BambuNetwork ${bridgePrintMethod} command for ${threeMfFilename} sent successfully.`,
1028
+ message: `FULU BambuNetwork ${bridgeMethod.replace(/^net\./, "")} command for ${threeMfFilename} sent successfully.${firstAttempt}`,
1016
1029
  connectionMode: "bambu_network",
1017
1030
  connectionType,
1018
1031
  bridgeMethod,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-3d-printer-server",
3
3
  "mcpName": "io.github.DMontgomery40/mcp-3d-printer-server",
4
- "version": "1.2.28",
4
+ "version": "1.2.29",
5
5
  "description": "MCP server for connecting Claude with 3D printer management systems",
6
6
  "main": "dist/index.js",
7
7
  "repository": "https://github.com/DMontgomery40/mcp-3D-printer-server",
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/DMontgomery40/mcp-3D-printer-server",
8
8
  "source": "github"
9
9
  },
10
- "version": "1.2.28",
10
+ "version": "1.2.29",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "mcp-3d-printer-server",
15
- "version": "1.2.28",
15
+ "version": "1.2.29",
16
16
  "runtimeHint": "npx",
17
17
  "transport": {
18
18
  "type": "stdio"
@@ -23,8 +23,16 @@ const DEFAULT_REQUEST_TIMEOUT_MS = 120_000;
23
23
  const LOCAL_CONNECT_STATUS = ["ok", "failed", "lost"];
24
24
  // bambu_networking.hpp BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED.
25
25
  const LOCAL_PRINT_PUBLISH_FAILED = -4030;
26
+ // A with-record send falls back to start_print only on its own preparation failures (the plug-in's
27
+ // PRINT_WR_* results, -2010 to -2140: project, upload, task record, FTPS), which come before the print
28
+ // command, so the printer has no job. Any other result may have reached the printer and is never
29
+ // replayed. Of those, Studio also stops on a file missing or too large (should_stop_without_fallback).
30
+ const WITH_RECORD_PREPARATION_FAILURE = (value: number) => value <= -2010 && value >= -2140 && value !== -2040 && value !== -2100;
26
31
  // bambu_networking.hpp print errors that name a recoverable cause.
27
32
  const BAMBU_NETWORK_PRINT_ERRORS: Record<number, string> = {
33
+ [-2120]: "Bambu Cloud refused to create the print task",
34
+ [-2130]: "FTPS upload to the printer failed",
35
+ [-3120]: "Bambu Cloud refused to create the print task",
28
36
  [-4010]: "the file exceeds the 1 GB upload limit",
29
37
  [-4020]: "FTPS upload to the printer failed",
30
38
  [LOCAL_PRINT_PUBLISH_FAILED]: "the networking plug-in could not send the print command to the printer over MQTT",
@@ -853,10 +861,24 @@ export class BambuNetworkBridge {
853
861
  }
854
862
  }
855
863
 
856
- /** A cloud print method (start_print) in a cloud session, so the plug-in has the printer's state. */
864
+ /**
865
+ * A cloud print in a cloud session, so the plug-in has the printer's state. With a fallback this is
866
+ * Bambu Studio's order for a cloud-bound printer it can also reach on the LAN (PrintJob.cpp,
867
+ * SendAttemptRoute::LanWithRecord, then ::Cloud): start_local_print_with_record, then start_print
868
+ * when the first failed before its print command was sent. `firstFailure` is the first method's result.
869
+ */
857
870
  async startCloudPrint(devId: string, method: string, payload: JsonObject, options: BambuNetworkBridgeOptions = {},
858
- assertActive: () => void = () => {}, timing: Readonly<CloudTiming> = CLOUD_PRINT_TIMING): Promise<{ result: unknown; jobUpdates: BridgeJobUpdate[] }> {
859
- return await this.withCloudSession(devId, options, assertActive, timing, async session => ({ result: await this.callWithAgent(method, payload, options), jobUpdates: session.jobUpdates }));
871
+ assertActive: () => void = () => {}, timing: Readonly<CloudTiming> = CLOUD_PRINT_TIMING, fallbackMethod?: string
872
+ ): Promise<{ result: unknown; jobUpdates: BridgeJobUpdate[]; firstFailure?: number }> {
873
+ return await this.withCloudSession(devId, options, assertActive, timing, async session => {
874
+ const result = await this.callWithAgent(method, payload, options);
875
+ const value = bridgeNumericResult(result);
876
+ if (!fallbackMethod || value === undefined || !WITH_RECORD_PREPARATION_FAILURE(value)) return { result, jobUpdates: session.jobUpdates };
877
+ options.trace?.({ step: "fallback", from: method, to: fallbackMethod, result: value });
878
+ const params = (payload as { params?: JsonObject }).params;
879
+ const fallbackPayload = params ? { ...payload, params: { ...params, comments: `failed(${value})` } } : payload;
880
+ return { result: await this.callWithAgent(fallbackMethod, fallbackPayload, options), jobUpdates: session.jobUpdates, firstFailure: value };
881
+ });
860
882
  }
861
883
 
862
884
  /**
package/src/index.ts CHANGED
@@ -75,7 +75,7 @@ import { BambuNetworkBridge, describeBambuNetworkPrintError, type BridgeJobUpdat
75
75
  import { PRINT_ROUTES, connectionForRoute, routeOf, routeOptions, writeEnvValues, type PrintRoute } from "./counterpart/route.js";
76
76
  import { resolveStudioPrintOptions, usedNozzleCount, type CallerPrintOptions } from "./studio-print-options.js";
77
77
  import { inspectPrintFile } from "./safety/print-file.js";
78
- import { validatePrinterState } from "./safety/printer-state.js";
78
+ import { externalStorageState, validatePrinterState } from "./safety/printer-state.js";
79
79
  import { uniquePrintName, withPrinterOperation, withPrintSnapshot } from "./safety/artifact.js";
80
80
  import { checkedFile } from "./printers/bambu.js";
81
81
  import { CONFIRMATION_UNAVAILABLE, confirmationTimedOut, confirmationTimeoutMs } from "./safety/confirmation.js";
@@ -1074,8 +1074,10 @@ class ThreeDPrinterMCPServer {
1074
1074
  throw new Error("FULU BambuNetwork printing requires bambu_serial, the printer host and its LAN access code: fresh printer state is checked over MQTT before every bridge print, including cloud dispatch.");
1075
1075
  }
1076
1076
  const isLocalBridgePrint = connectionType === "lan";
1077
- const bridgePrintMethod = isLocalBridgePrint ? "start_local_print" : "start_print";
1078
- const bridgeMethod = `net.${bridgePrintMethod}`;
1077
+ // Bambu Studio sends a cloud-bound printer it can reach on the LAN with record first, in the cloud
1078
+ // session, then start_print (PrintJob.cpp). This server always has the printer's IP and access code.
1079
+ let bridgeMethod = isLocalBridgePrint ? "net.start_local_print" : "net.start_local_print_with_record";
1080
+ let cloudFallbackMethod = isLocalBridgePrint ? undefined : "net.start_print";
1079
1081
  const { plateIndex } = options;
1080
1082
  const threeMfFilename = path.basename(options.threeMFPath);
1081
1083
  const projectName = threeMfFilename.replace(/\.3mf$/i, "");
@@ -1116,6 +1118,11 @@ class ThreeDPrinterMCPServer {
1116
1118
  validatePrinterState(await bambu.getSafetyStatus(devIp, devId, password), requirements);
1117
1119
  const confirmedStatus = await bambu.getSafetyStatus(devIp, devId, password);
1118
1120
  validatePrinterState(confirmedStatus, requirements);
1121
+ if (cloudFallbackMethod && externalStorageState(confirmedStatus.raw) !== "normal") {
1122
+ // Studio sends straight through the cloud when the printer has no storage for the LAN upload.
1123
+ bridgeMethod = cloudFallbackMethod;
1124
+ cloudFallbackMethod = undefined;
1125
+ }
1119
1126
  const wireMapping = mapping!.map((value) => bridgeAmsMappingValue(value, dualNozzle));
1120
1127
  const remoteFileName = uniquePrintName(options.threeMFPath);
1121
1128
  const checkedName = remoteFileName.replace(/(?:\.gcode)?\.3mf$/i, "");
@@ -1172,22 +1179,28 @@ class ThreeDPrinterMCPServer {
1172
1179
  const bridgeOptions = {
1173
1180
  // Trusted in-process preflight immediately before the print frame.
1174
1181
  beforeDispatch: async (method: string) => {
1175
- if (method !== bridgeMethod) return;
1182
+ if (method !== bridgeMethod && method !== cloudFallbackMethod) return;
1176
1183
  assertActive();
1177
1184
  validatePrinterState(await bambu.getSafetyStatus(devIp, devId, password), requirements);
1178
1185
  },
1179
- assertDispatchAllowed: (method: string) => { if (method === bridgeMethod) assertActive(); },
1180
- onDispatched: (method: string) => { if (method === bridgeMethod) retainForDispatchedUpload(); },
1186
+ assertDispatchAllowed: (method: string) => { if (method === bridgeMethod || method === cloudFallbackMethod) assertActive(); },
1187
+ onDispatched: (method: string) => { if (method === bridgeMethod || method === cloudFallbackMethod) retainForDispatchedUpload(); },
1181
1188
  };
1182
1189
  let jobUpdates: BridgeJobUpdate[] = [];
1183
1190
  let bridgeResult: unknown;
1191
+ let firstAttempt = "";
1184
1192
  if (isLocalBridgePrint) {
1185
1193
  ({ result: bridgeResult, jobUpdates } = await this.bambuNetwork.startLocalPrint(
1186
1194
  { devId, devIp, username: "bblp", password, useSsl: true },
1187
1195
  bridgeMethod, { client_job_id: clientJobId, params }, bridgeOptions, assertActive
1188
1196
  ));
1189
1197
  } else {
1190
- ({ result: bridgeResult, jobUpdates } = await this.bambuNetwork.startCloudPrint(devId, bridgeMethod, { client_job_id: clientJobId, params }, bridgeOptions, assertActive));
1198
+ let firstFailure: number | undefined;
1199
+ ({ result: bridgeResult, jobUpdates, firstFailure } = await this.bambuNetwork.startCloudPrint(devId, bridgeMethod, { client_job_id: clientJobId, params }, bridgeOptions, assertActive, undefined, cloudFallbackMethod));
1200
+ if (firstFailure !== undefined) {
1201
+ bridgeMethod = cloudFallbackMethod!;
1202
+ firstAttempt = ` It first tried net.start_local_print_with_record, as Bambu Studio does, which returned ${firstFailure}${describeBambuNetworkPrintError(firstFailure)}.`;
1203
+ }
1191
1204
  }
1192
1205
  if (typeof bridgeResult === "object" && bridgeResult !== null && (bridgeResult as { ok?: unknown }).ok === false) {
1193
1206
  throw new Error(`FULU BambuNetwork bridge method ${bridgeMethod} failed: ${String((bridgeResult as { error?: unknown }).error || "unknown bridge error")}`);
@@ -1197,14 +1210,14 @@ class ThreeDPrinterMCPServer {
1197
1210
  const reported = jobUpdates.filter((update) => update.code !== 0 || update.msg).slice(-3)
1198
1211
  .map((update) => `status ${update.status}, code ${update.code}${update.msg ? `: ${update.msg}` : ""}`).join("; ");
1199
1212
  throw new Error(`FULU BambuNetwork bridge method ${bridgeMethod} returned non-zero result ${value}${describeBambuNetworkPrintError(value)}.` +
1200
- (reported ? ` Plug-in reported: ${reported}.` : ""));
1213
+ (reported ? ` Plug-in reported: ${reported}.` : "") + firstAttempt);
1201
1214
  }
1202
1215
  assertActive();
1203
1216
  // Bambu's plug-in placed the file, so a resume re-reads it if it can and otherwise asks a person.
1204
1217
  bambu.recordCheckedJob(devIp, devId, remoteFileName, requirements, resumeFile, false);
1205
1218
  return {
1206
1219
  status: "success",
1207
- message: `FULU BambuNetwork ${bridgePrintMethod} command for ${threeMfFilename} sent successfully.`,
1220
+ message: `FULU BambuNetwork ${bridgeMethod.replace(/^net\./, "")} command for ${threeMfFilename} sent successfully.${firstAttempt}`,
1208
1221
  connectionMode: "bambu_network",
1209
1222
  connectionType,
1210
1223
  bridgeMethod,