zas-agent 0.6.1 → 0.6.2

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,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.2] - 2026-09-04
11
+
12
+ ### Added
13
+
14
+ - The trace records the peer connection state, not only the ICE state.
15
+ ICE `connected` means a candidate pair answered; the peer connection
16
+ reaches `connected` only once DTLS completes. A session that holds an ICE
17
+ path and sends nothing over it used to look the same as one that never
18
+ found a path at all.
19
+ - A failure that had a working ICE pair is now `channel_never_opened`, not
20
+ `connect_timeout`. Same clock, different fault: one is the candidate hunt,
21
+ the other is the handshake above it.
22
+ - The verdict line carries the peer connection state and the data channel's
23
+ own state, so a stalled DTLS handshake and a stalled SCTP association can
24
+ be told apart from one line.
25
+ - The selected candidate pair is read when ICE connects, not only when the
26
+ data channel opens. A run that connected and then went quiet reported
27
+ `pair=none` and looked as if it had never selected one.
28
+ - `onicecandidateerror` is traced, by URL shape and error code only. A
29
+ gather that ends with no relay candidate now says whether the allocation
30
+ was refused or never answered.
31
+
10
32
  ## [0.6.1] - 2026-09-04
11
33
 
12
34
  ### Added
@@ -183,7 +205,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
183
205
  two separate identities that cannot read each other's keys.
184
206
  - Install snippets for Claude Code and Codex, printed by `zas-agent pair`.
185
207
 
186
- [Unreleased]: https://github.com/soke1556/zas-agent/compare/v0.6.1...HEAD
208
+ [Unreleased]: https://github.com/soke1556/zas-agent/compare/v0.6.2...HEAD
209
+ [0.6.2]: https://github.com/soke1556/zas-agent/compare/v0.6.1...v0.6.2
187
210
  [0.6.1]: https://github.com/soke1556/zas-agent/compare/v0.6.0...v0.6.1
188
211
  [0.6.0]: https://github.com/soke1556/zas-agent/compare/v0.5.1...v0.6.0
189
212
  [0.5.1]: https://github.com/soke1556/zas-agent/compare/v0.5.0...v0.5.1
package/dist/cli.js CHANGED
@@ -969,14 +969,17 @@ function traceAdd(diag, line) {
969
969
  const at = t0 === void 0 ? 0 : (Date.now() - t0) / 1e3;
970
970
  diag.trace.push(`${at.toFixed(2)} ${line}`);
971
971
  }
972
+ function urlShape(url) {
973
+ const scheme = /^(stuns?|turns?):/i.exec(url)?.[1]?.toLowerCase() ?? "other";
974
+ const transport = /[?&]transport=(\w+)/i.exec(url)?.[1]?.toLowerCase();
975
+ return transport ? `${scheme}/${transport}` : scheme;
976
+ }
972
977
  function iceShapes(servers) {
973
978
  const seen = /* @__PURE__ */ new Map();
974
979
  for (const server of servers ?? []) {
975
980
  const urls = typeof server.urls === "string" ? [server.urls] : server.urls;
976
981
  for (const url of urls ?? []) {
977
- const scheme = /^(stuns?|turns?):/i.exec(url)?.[1]?.toLowerCase() ?? "other";
978
- const transport = /[?&]transport=(\w+)/i.exec(url)?.[1]?.toLowerCase();
979
- const shape = transport ? `${scheme}/${transport}` : scheme;
982
+ const shape = urlShape(url);
980
983
  seen.set(shape, (seen.get(shape) ?? 0) + 1);
981
984
  }
982
985
  }
@@ -1081,6 +1084,15 @@ function session(ice, onPhase, onDiag, iceTransportPolicy = "all") {
1081
1084
  pc.onicegatheringstatechange = () => {
1082
1085
  traceAdd(diag, `gather ${String(pc.iceGatheringState ?? "")}`);
1083
1086
  };
1087
+ const connState = () => String(pc.connectionState ?? "");
1088
+ pc.onconnectionstatechange = () => {
1089
+ traceAdd(diag, `conn ${connState()}`);
1090
+ };
1091
+ let channel;
1092
+ pc.onicecandidateerror = (e) => {
1093
+ const err = e;
1094
+ traceAdd(diag, `ice error ${err.url ? urlShape(err.url) : "?"} code=${err.errorCode ?? "?"}`);
1095
+ };
1084
1096
  const cleanup = () => {
1085
1097
  clearTimeout(waitTimer);
1086
1098
  clearTimeout(connectTimer);
@@ -1089,6 +1101,8 @@ function session(ice, onPhase, onDiag, iceTransportPolicy = "all") {
1089
1101
  pc.onicecandidate = null;
1090
1102
  pc.oniceconnectionstatechange = null;
1091
1103
  pc.onicegatheringstatechange = null;
1104
+ pc.onicecandidateerror = null;
1105
+ pc.onconnectionstatechange = null;
1092
1106
  pc.close();
1093
1107
  };
1094
1108
  const setPhase = (p) => {
@@ -1100,7 +1114,10 @@ function session(ice, onPhase, onDiag, iceTransportPolicy = "all") {
1100
1114
  diag.gatherState = String(pc.iceGatheringState ?? "");
1101
1115
  diag.hadRemoteDesc = hasRemoteDescription(pc);
1102
1116
  diag.turnUrlsConfigured = countTurnUrls(pc.getConfiguration().iceServers);
1103
- traceAdd(diag, `${p}${diag.reason ? ` ${diag.reason}` : ""} ice=${diag.iceState} gather=${diag.gatherState}`);
1117
+ traceAdd(
1118
+ diag,
1119
+ `${p}${diag.reason ? ` ${diag.reason}` : ""} ice=${diag.iceState} gather=${diag.gatherState} conn=${connState()} chan=${channel === void 0 ? "-" : String(channel.readyState ?? "")}`
1120
+ );
1104
1121
  onDiag?.(diag);
1105
1122
  onPhase(p);
1106
1123
  cleanup();
@@ -1124,7 +1141,10 @@ function session(ice, onPhase, onDiag, iceTransportPolicy = "all") {
1124
1141
  if (heard) return;
1125
1142
  heard = true;
1126
1143
  clearTimeout(waitTimer);
1127
- connectTimer = setTimeout(() => fail("connect_timeout"), DIRECT_CONNECT_TIMEOUT_MS);
1144
+ connectTimer = setTimeout(() => {
1145
+ const iceUp = pc.iceConnectionState === "connected" || pc.iceConnectionState === "completed";
1146
+ fail(iceUp ? "channel_never_opened" : "connect_timeout");
1147
+ }, DIRECT_CONNECT_TIMEOUT_MS);
1128
1148
  };
1129
1149
  let onStall;
1130
1150
  let stallWindowMs = DIRECT_CONNECT_TIMEOUT_MS;
@@ -1158,6 +1178,13 @@ function session(ice, onPhase, onDiag, iceTransportPolicy = "all") {
1158
1178
  clearTimeout(disconnectTimer);
1159
1179
  clearTimeout(restartTimer);
1160
1180
  stallState = "idle";
1181
+ void pc.getStats().then((stats) => {
1182
+ const pair = pairFromStats(stats);
1183
+ if (!pair || diag.pairLocal) return;
1184
+ diag.pairLocal = pair.local;
1185
+ diag.pairRemote = pair.remote;
1186
+ traceAdd(diag, `pair ${pair.local}/${pair.remote}`);
1187
+ }).catch(() => void 0);
1161
1188
  }
1162
1189
  };
1163
1190
  return {
@@ -1176,6 +1203,7 @@ function session(ice, onPhase, onDiag, iceTransportPolicy = "all") {
1176
1203
  }
1177
1204
  },
1178
1205
  isTerminal: () => phase === "done" || phase === "failed",
1206
+ setChannel: (dc) => void (channel = dc),
1179
1207
  setStall: (fn, windowMs) => {
1180
1208
  onStall = fn;
1181
1209
  stallWindowMs = windowMs;
@@ -1267,6 +1295,7 @@ function startSender(opts) {
1267
1295
  })().catch((e) => s.fail("signaling", e));
1268
1296
  }, DIRECT_CONNECT_TIMEOUT_MS);
1269
1297
  const dc = pc.createDataChannel("zas-direct", { ordered: true });
1298
+ s.setChannel(dc);
1270
1299
  dc.binaryType = "arraybuffer";
1271
1300
  dc.bufferedAmountLowThreshold = DIRECT_BUFFERED_HIGH / 8;
1272
1301
  const waitLow = () => new Promise((resolve2) => {
@@ -1415,6 +1444,7 @@ function startReceiver(opts) {
1415
1444
  let finished = false;
1416
1445
  pc.ondatachannel = (e) => {
1417
1446
  dc = e.channel;
1447
+ s.setChannel(dc);
1418
1448
  dc.binaryType = "arraybuffer";
1419
1449
  dc.onopen = () => {
1420
1450
  s.connected();
@@ -3898,7 +3928,7 @@ async function report(client, properties) {
3898
3928
 
3899
3929
  // src/server.ts
3900
3930
  function agentVersion() {
3901
- return true ? "0.6.1" : "0.0.0-dev";
3931
+ return true ? "0.6.2" : "0.0.0-dev";
3902
3932
  }
3903
3933
  var AGENT_CUE = " The owner sees every item this agent sends with the >_ agent mark and this agent's name, on every device.";
3904
3934
  var PAIR_ANNOUNCE_MS = 15e3;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zas-agent",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "MCP server that lets a coding agent send files and notes into your Zas channels, and send or receive files live through Directo.",
5
5
  "type": "module",
6
6
  "license": "MIT",