qiksy-mcp 1.63.0 → 1.64.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.mjs +48 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qiksy-mcp",
3
- "version": "1.63.0",
3
+ "version": "1.64.0",
4
4
  "description": "Browser MCP server for the Chrome tab you already have open — your session, your logins. Gives Claude Code, Cursor, Codex and VS Code the live page: findings, forms, failed requests with server bodies.",
5
5
  "keywords": [
6
6
  "mcp",
package/server.mjs CHANGED
@@ -1292,6 +1292,11 @@ function onConnection(ws, req) {
1292
1292
  // answer back under the follower's own id. Ours is a fresh randomUUID, so the two
1293
1293
  // id spaces cannot collide.
1294
1294
  if (msg.t === 'req' && isFollower) {
1295
+ /* A REAL full stop from a sibling window or from `npx qiksy-mcp done` — the Stop hook's
1296
+ route into this process. Our OWN estimate travels the same way when a follower guesses,
1297
+ which is why the flag it carries is checked here: a guess must not teach the hub that
1298
+ this setup reports its endings. */
1299
+ if (msg.tool === 'qa_done' && !msg.args?.guessed) noteRealDone();
1295
1300
  callExtension(msg.tool, msg.args, msg.timeoutMs)
1296
1301
  .then((result) => safeSend(ws, { t: 'res', id: msg.id, ok: true, result }))
1297
1302
  .catch((err) => safeSend(ws, { t: 'res', id: msg.id, ok: false, error: err?.message || String(err) }));
@@ -1315,51 +1320,69 @@ function onConnection(ws, req) {
1315
1320
  let uiLanguage = '';
1316
1321
 
1317
1322
  /**
1318
- * THE END OF A RUN, WITHOUT ANYBODY HAVING TO REMEMBER IT.
1323
+ * GUESSING THE END OF A RUN — OFF BY DEFAULT SINCE 1.64.0, AND THE REASON IS WORTH KEEPING.
1319
1324
  *
1320
- * `qa_done` is the verb that makes the tab pulse and the sound play, and the agent is asked to
1321
- * call it in the instructions handed over at connect. An instruction is a request, though, not a
1322
- * mechanism: some clients follow it, some do not, and every agent already installed has the old
1323
- * habit. So the person updates, everything is switched on by default — and hears nothing. That is
1324
- * the state this exists to prevent (owner, 21.09.2026).
1325
+ * It was added on 21.09.2026 to cover clients that never call `qa_done`: the person updates,
1326
+ * everything is on, and they hear nothing. The guess is silence — no bridge call for a while means
1327
+ * the run is over — and it was defended as safe to be wrong, because a later call cancels the
1328
+ * pulse and the extension refuses to sound twice in half a minute.
1325
1329
  *
1326
- * What this process CAN observe is the same thing the voice already watches: it timestamps every
1327
- * call and knows when the last one finished. A run that did real work and then went quiet past a
1328
- * threshold is over in every sense that matters to somebody in the next room.
1330
+ * THAT DEFENCE ONLY COVERED THE PULSE. A sound cannot be cancelled after it has been heard, and
1331
+ * the silence it keys on is not silence at all: an agent that walked away from the browser to read
1332
+ * files, edit code or think makes no bridge call for MINUTES while working flat out. So the signal
1333
+ * arrived mid-run, and again on the next lull, and again — which is exactly what the owner
1334
+ * reported hearing (28.09.2026: «звук отрабатывает, а агент продолжает маслать… QADone несколько
1335
+ * раз отрабатывает, то есть он не по окончанию»). His ruling covers the mark as well as the
1336
+ * sound: both announce the END of a task, so neither may fire on a guess.
1329
1337
  *
1330
- * IT IS AN ESTIMATE, AND IT IS BUILT TO BE WRONG SAFELY. Three things make a premature signal
1331
- * cheap rather than annoying, and none of them are new — they all shipped with the verb:
1332
- * · the extension refuses to sound when no work has been recorded;
1333
- * · it refuses to sound twice within half a minute;
1334
- * · ANY later call stops the pulse, so an agent that was merely thinking cancels the signal by
1335
- * carrying on — the mistake undoes itself without anybody touching anything.
1336
- * The exact path — a Stop hook running `npx qiksy-mcp done` — stays available for clients that
1337
- * have one, and is what `init` writes. This is the floor under it, not a replacement.
1338
+ * A signal that is right most of the time is worse than no signal, because it is learned as noise
1339
+ * and then switched off — taking the true ones with it.
1340
+ *
1341
+ * WHAT REPLACES IT IS NOT NOTHING. Two routes report a real full stop, and `connect` writes the
1342
+ * second one into the client's settings, so an ordinary install has it:
1343
+ * · the agent calling `qa_done` itself, which every client is told about at `initialize`;
1344
+ * · a Stop hook running `npx qiksy-mcp done`, which fires whether or not the model remembered.
1345
+ * The guess survives as an opt-in for a client that has neither: `QIKSY_MCP_DONE_AFTER=<seconds>`.
1338
1346
  */
1339
1347
  const DONE_AFTER_MS = (() => {
1340
1348
  const raw = process.env.QIKSY_MCP_DONE_AFTER;
1341
- if (raw === '0' || /^(off|no|false)$/i.test(raw || '')) return 0;
1342
1349
  const n = Number(raw);
1343
1350
  /* Below twenty seconds this would fire inside an ordinary pause for thought, so a smaller
1344
- number is read as a mistake and the default is used instead. */
1345
- return Number.isFinite(n) && n >= 20 ? n * 1000 : 90_000;
1351
+ number is refused rather than honoured. Anything else, including nothing at all, is off. */
1352
+ return Number.isFinite(n) && n >= 20 ? n * 1000 : 0;
1346
1353
  })();
1347
1354
  let doneTimer = null;
1348
1355
  let workSinceDone = false;
1356
+ /**
1357
+ * A SETUP THAT REPORTS ITS OWN FULL STOP NEEDS NO GUESS, and the guess is the part that can be
1358
+ * wrong. One real `qa_done` proves this client has a working route, so the estimate stands down
1359
+ * for the rest of the process — nobody has to notice that they opted into both.
1360
+ */
1361
+ let realDoneSeen = false;
1362
+
1363
+ /** Called where a REAL full stop enters this process, and nowhere else — never from `fireDone`. */
1364
+ function noteRealDone() {
1365
+ if (realDoneSeen) return;
1366
+ realDoneSeen = true;
1367
+ if (doneTimer) clearTimeout(doneTimer);
1368
+ doneTimer = null;
1369
+ workSinceDone = false;
1370
+ if (DONE_AFTER_MS) log('▸ this setup announces the end of a run itself — the quiet-timer guess is off from here on');
1371
+ }
1349
1372
 
1350
1373
  function fireDone() {
1351
1374
  doneTimer = null;
1352
- if (!workSinceDone) return;
1375
+ if (!workSinceDone || realDoneSeen) return;
1353
1376
  workSinceDone = false;
1354
1377
  log(`▸ quiet for ${Math.round(DONE_AFTER_MS / 1000)}s after a run — saying it has finished`);
1355
1378
  /* Failure here is nothing: the browser may have gone, the tab may have closed. The pulse is a
1356
1379
  courtesy, and a courtesy that throws is worse than one that does not happen. */
1357
- callExtension('qa_done', { verdict: 'ok' }, 8_000).catch(() => {});
1380
+ callExtension('qa_done', { verdict: 'ok', guessed: true }, 8_000).catch(() => {});
1358
1381
  }
1359
1382
 
1360
1383
  /** Called when a call FINISHES, not when it starts: a slow verb must not look like silence. */
1361
1384
  function armDone(tool) {
1362
- if (!DONE_AFTER_MS) return;
1385
+ if (!DONE_AFTER_MS || realDoneSeen) return;
1363
1386
  if (doneTimer) clearTimeout(doneTimer);
1364
1387
  doneTimer = null;
1365
1388
  if (tool === 'qa_done') {
@@ -7147,6 +7170,8 @@ server.registerTool(
7147
7170
  },
7148
7171
  async ({ tabId, verdict }) => {
7149
7172
  try {
7173
+ /* The agent said it itself — the one route that needs no guessing behind it. */
7174
+ noteRealDone();
7150
7175
  return asText(await callExtension('qa_done', { tabId, verdict }, 15_000));
7151
7176
  } catch (e) {
7152
7177
  /* The verb lives in the EXTENSION, which updates on the Web Store's clock while this server