arisa 5.1.9 → 5.1.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arisa",
3
- "version": "5.1.9",
3
+ "version": "5.1.10",
4
4
  "description": "Telegram + Pi Agent modular assistant",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -165,6 +165,14 @@ export function formatSlaveStatus({ systemd, diagnostic }) {
165
165
  ].join("\n");
166
166
  }
167
167
 
168
+ function explainSlaveBootstrapError(error) {
169
+ if (error?.message !== "Socket closed before the protocol completed") return error;
170
+ return new Error(
171
+ "Master ended the pairing handshake before it completed. The bootstrap URL may be expired, rotated, already used, or invalid. Generate a new bootstrap URL on Master and retry before it expires.",
172
+ { cause: error }
173
+ );
174
+ }
175
+
168
176
  export async function runSlaveBootstrap(url, {
169
177
  paths = getSlavePaths(resolveSlaveHome()),
170
178
  ask = askFromTerminal,
@@ -181,11 +189,16 @@ export async function runSlaveBootstrap(url, {
181
189
  const account = await selectAccount({ ask });
182
190
  await ensureSlaveConfig(paths);
183
191
  await ensureTool(paths);
184
- const result = await withSecureRequestFile({
185
- directory: paths.tmpDir,
186
- prefix: "bootstrap",
187
- value: { url }
188
- }, (bootstrapFile) => invokeTool(paths, { action: "slave.bootstrap", bootstrapFile }));
192
+ let result;
193
+ try {
194
+ result = await withSecureRequestFile({
195
+ directory: paths.tmpDir,
196
+ prefix: "bootstrap",
197
+ value: { url }
198
+ }, (bootstrapFile) => invokeTool(paths, { action: "slave.bootstrap", bootstrapFile }));
199
+ } catch (error) {
200
+ throw explainSlaveBootstrapError(error);
201
+ }
189
202
  await installService({ account, slaveHome: paths.home, entryFile });
190
203
  await writeSlaveServiceDescriptor(paths, { version: 1, account, installedAt: new Date().toISOString() });
191
204
  output.log(`Arisa Slave paired and running as ${account.user}${account.root ? " (root)" : ""}.`);
@@ -176,6 +176,26 @@ test("validates before effects and keeps the bootstrap secret out of service met
176
176
  assert.deepEqual(calls, []);
177
177
  });
178
178
 
179
+ test("explains an incomplete Master pairing handshake", async (t) => {
180
+ const home = await mkdtemp(path.join(os.tmpdir(), "arisa-slave-handshake-"));
181
+ t.after(() => rm(home, { recursive: true, force: true }));
182
+ await assert.rejects(
183
+ () => runSlaveBootstrap(`tcp://198.51.100.12:4719/${secret}`, {
184
+ paths: getSlavePaths(home),
185
+ entryFile: "/opt/arisa/src/index.js",
186
+ platform: "linux",
187
+ selectAccount: async () => ({ scope: "user", user: "tester", root: false, dedicated: false }),
188
+ ensureTool: async () => {},
189
+ installService: async () => assert.fail("service must not be installed after a failed handshake"),
190
+ invokeTool: async () => {
191
+ throw new Error("Socket closed before the protocol completed");
192
+ },
193
+ output: { log: () => {} }
194
+ }),
195
+ /bootstrap URL may be expired, rotated, already used, or invalid.*before it expires/i
196
+ );
197
+ });
198
+
179
199
  test("ships an immutable verified master-slave bootstrap lock", async (t) => {
180
200
  const home = await mkdtemp(path.join(os.tmpdir(), "arisa-slave-lock-"));
181
201
  t.after(() => rm(home, { recursive: true, force: true }));