@lawaty/lacode 1.0.1 → 1.0.3

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
@@ -7,6 +7,38 @@ All notable changes to LaCode are recorded here. The format follows
7
7
  Versions come from git release commits; the npm version is bumped in the same
8
8
  commit as the changelog entry it describes.
9
9
 
10
+ ## [1.0.3] — one notification even when the plugin is loaded twice
11
+
12
+ ### Fixed
13
+
14
+ - **A question or permission popup is raised once when two LaCode instances share
15
+ a process.** opencode auto-discovers every file under
16
+ `~/.config/opencode/plugins/`, so a host that also lists `@lawaty/lacode` in
17
+ `opencode.jsonc` loaded two copies. `done` was already deduplicated by the
18
+ completion marker, but the notification claim treated any claim holding our
19
+ own pid as ours — indistinguishable between two instances in one process. The
20
+ claim now carries a per-instance token, so the second instance loses while the
21
+ same instance may still raise a new prompt.
22
+
23
+ ### Changed
24
+
25
+ - **Publishing syncs the published version to every host.** `postpublish` runs
26
+ `scripts/sync-hosts.mjs`: it installs `@lawaty/lacode@<version>`, normalises
27
+ each host's config to a single LaCode entry (preserving its options), disables
28
+ stale auto-discovered shims, and warns when a host cannot run the helper.
29
+
30
+ ## [1.0.2] — the permission chime plays when the popup appears
31
+
32
+ ### Fixed
33
+
34
+ - **An actionable notification no longer delays its sound until it is answered.**
35
+ `sendLocal()` played the chime after `notify-send` returned, but an actionable
36
+ send (`--action` implies `--wait`) blocks until the user clicks a button or
37
+ dismisses the popup. The chime therefore played at click time — and never at
38
+ all when the popup was left alone — which made permission sounds look
39
+ intermittent. The sound now starts before the notifier runs, and a test pins
40
+ the order with a blocking fake notifier.
41
+
10
42
  ## [1.0.1] — notification sounds you can hear, and a title that names the workspace
11
43
 
12
44
  ### Fixed
package/dist/index.js CHANGED
@@ -3662,6 +3662,7 @@ function scrubSlotNames(text) {
3662
3662
 
3663
3663
  // src/plugins/notify.ts
3664
3664
  import { spawn as spawn2 } from "node:child_process";
3665
+ import { randomUUID as randomUUID2 } from "node:crypto";
3665
3666
  import { existsSync as existsSync3 } from "node:fs";
3666
3667
  import * as path3 from "node:path";
3667
3668
 
@@ -3857,7 +3858,7 @@ var isPidAlive2 = (pid) => {
3857
3858
  }
3858
3859
  };
3859
3860
  var claimFile = (sessionID, kind) => join7(notificationStateDir(), `claim.${safeCorrelationKey(`${sessionID}.${kind}`)}`);
3860
- var claimNotification = (sessionID, kind) => {
3861
+ var claimNotification = (sessionID, kind, instanceId = "") => {
3861
3862
  const path4 = claimFile(sessionID, kind);
3862
3863
  try {
3863
3864
  mkdirSync5(notificationStateDir(), { recursive: true });
@@ -3868,7 +3869,7 @@ var claimNotification = (sessionID, kind) => {
3868
3869
  try {
3869
3870
  const fd = openSync2(path4, "wx");
3870
3871
  try {
3871
- writeSync2(fd, String(process.pid));
3872
+ writeSync2(fd, `${process.pid} ${instanceId} ${kind}`);
3872
3873
  } finally {
3873
3874
  closeSync2(fd);
3874
3875
  }
@@ -3888,13 +3889,21 @@ var claimNotification = (sessionID, kind) => {
3888
3889
  } catch (error) {
3889
3890
  return error.code === "ENOENT" ? "gone" : "unknown";
3890
3891
  }
3892
+ const fields = raw.split(/\s+/).filter((entry) => entry.length > 0);
3891
3893
  let holder;
3892
3894
  const empty = raw === "";
3893
3895
  if (!empty) {
3894
- const parsed = Number.parseInt(raw, 10);
3896
+ const parsed = Number.parseInt(fields[0] ?? "", 10);
3895
3897
  holder = Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
3896
3898
  }
3897
- if (holder === process.pid) return "ours";
3899
+ if (holder === process.pid) {
3900
+ const hasToken = fields.length >= 3;
3901
+ const fileToken = hasToken ? fields[1] ?? "" : "";
3902
+ if (hasToken && instanceId !== "" && fileToken !== instanceId) {
3903
+ return "held";
3904
+ }
3905
+ return "ours";
3906
+ }
3898
3907
  let age = Number.POSITIVE_INFINITY;
3899
3908
  try {
3900
3909
  age = Date.now() - statSync5(path4).mtimeMs;
@@ -4050,6 +4059,7 @@ var notifyHooks = (async ({ client, directory }, options, deps = {}) => {
4050
4059
  const soundEnv = (process.env[SOUND_ENV] ?? "").trim().toLowerCase();
4051
4060
  const soundOn = !SOUND_OFF_VALUES.includes(soundEnv) && opts.sound !== false;
4052
4061
  const notifier = new Notifier(COOLDOWN_MS2);
4062
+ const instanceId = randomUUID2();
4053
4063
  const active = /* @__PURE__ */ new Set();
4054
4064
  const titles = /* @__PURE__ */ new Map();
4055
4065
  const pending = /* @__PURE__ */ new Set();
@@ -4345,7 +4355,7 @@ var notifyHooks = (async ({ client, directory }, options, deps = {}) => {
4345
4355
  void log("debug", "suppressed: completion already announced", { sessionID });
4346
4356
  return;
4347
4357
  }
4348
- } else if (!claimNotification(sessionID, kind)) {
4358
+ } else if (!claimNotification(sessionID, kind, instanceId)) {
4349
4359
  void log("debug", "suppressed: another process is raising this", { sessionID, kind });
4350
4360
  return;
4351
4361
  } else if (outcome === "error") {
package/dist/lacode.js CHANGED
@@ -369,8 +369,8 @@ function sendLocal(request, platform = process.platform) {
369
369
  // Never auto-dismissed: the notification waits for the user to close it.
370
370
  [title, body, APP_NAME_FLAG, `--urgency=${normalizeUrgency(request.urgency)}`, `--icon=${normalizeIcon(request.icon)}`, NEVER_EXPIRE_FLAG, ...trackable ? ["--print-id"] : [], ...actionArgs]
371
371
  );
372
- const result = run(CHANNEL_BINARY[channel], args, actionable ? ACTION_WAIT_MS : 5e3);
373
372
  playSound();
373
+ const result = run(CHANNEL_BINARY[channel], args, actionable ? ACTION_WAIT_MS : 5e3);
374
374
  const printed = trackable ? splitNotifyOutput(result.stdout) : { id: "", action: result.stdout.trim() };
375
375
  const action = actionable ? printed.action : "";
376
376
  if (correlationKey !== void 0 && printed.id) recordNotificationId(correlationKey, printed.id);
package/dist/tunnel.js CHANGED
@@ -362,8 +362,8 @@ function sendLocal(request, platform = process.platform) {
362
362
  // Never auto-dismissed: the notification waits for the user to close it.
363
363
  [title, body, APP_NAME_FLAG, `--urgency=${normalizeUrgency(request.urgency)}`, `--icon=${normalizeIcon(request.icon)}`, NEVER_EXPIRE_FLAG, ...trackable ? ["--print-id"] : [], ...actionArgs]
364
364
  );
365
- const result = run(CHANNEL_BINARY[channel], args, actionable ? ACTION_WAIT_MS : 5e3);
366
365
  playSound();
366
+ const result = run(CHANNEL_BINARY[channel], args, actionable ? ACTION_WAIT_MS : 5e3);
367
367
  const printed = trackable ? splitNotifyOutput(result.stdout) : { id: "", action: result.stdout.trim() };
368
368
  const action = actionable ? printed.action : "";
369
369
  if (correlationKey !== void 0 && printed.id) recordNotificationId(correlationKey, printed.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lawaty/lacode",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Ultimate opencode setup for Go users — cost-controlled pooled subagents, hang recovery, and notifications for opencode.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -52,6 +52,7 @@
52
52
  "scripts": {
53
53
  "build": "node scripts/build.mjs",
54
54
  "prepack": "npm run build",
55
+ "postpublish": "node scripts/sync-hosts.mjs",
55
56
  "test": "node --test tests/",
56
57
  "benchmark": "node tests/benchmark/run.mjs"
57
58
  },