zas-agent 0.7.0 → 0.8.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 (3) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/dist/cli.js +22 -10
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-09-06
11
+
12
+ ### Added
13
+
14
+ - A channel you joined, but did not create, can now be granted to an agent.
15
+ Its owner has to allow the agent first. Until they answer, the grant is
16
+ written and waiting: the key is real and it opens nothing.
17
+ - `grant_pending`. A tool call on a channel whose owner has not answered says
18
+ so, and says to ask them, rather than reporting the grant as missing.
19
+ - `foreign_limit` on pairing. A plan allows an agent a number of channels of
20
+ other people, and the free plan allows fewer of those than of your own.
21
+ - `zas_status` marks such a channel as waiting for its owner.
22
+
23
+ ### Fixed
24
+
25
+ - Reading a channel somebody else created said `read_forbidden` on a grant
26
+ that plainly said `send · read`, while sending into the same channel
27
+ worked. Sending goes through the API, which knows which account holds a
28
+ channel; reading goes to Firestore directly, and the agent looked under
29
+ its own owner, where a shared channel is not kept. The server now says
30
+ where each channel lives, and reads, Directo offers and Directo fallbacks
31
+ all address that account. A channel of your own is unchanged: it is in
32
+ your own account, which is where it was always read from.
33
+
10
34
  ## [0.7.0] - 2026-09-05
11
35
 
12
36
  ### Added
@@ -279,7 +303,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
279
303
  two separate identities that cannot read each other's keys.
280
304
  - Install snippets for Claude Code and Codex, printed by `zas-agent pair`.
281
305
 
282
- [Unreleased]: https://github.com/soke1556/zas-agent/compare/v0.7.0...HEAD
306
+ [Unreleased]: https://github.com/soke1556/zas-agent/compare/v0.8.0...HEAD
307
+ [0.8.0]: https://github.com/soke1556/zas-agent/compare/v0.7.0...v0.8.0
283
308
  [0.7.0]: https://github.com/soke1556/zas-agent/compare/v0.6.3...v0.7.0
284
309
  [0.6.3]: https://github.com/soke1556/zas-agent/compare/v0.6.2...v0.6.3
285
310
  [0.6.2]: https://github.com/soke1556/zas-agent/compare/v0.6.1...v0.6.2
package/dist/cli.js CHANGED
@@ -159,6 +159,7 @@ var SENTENCES = {
159
159
  agent_forbidden: "This agent cannot do that; only the account owner can.",
160
160
  agent_revoked: "The owner revoked this agent. Pair again with \u201Czas-agent pair\u201D.",
161
161
  grant_missing: "This agent has no access to that channel. The owner adds it from Settings \u2192 Agents.",
162
+ grant_pending: "That channel belongs to somebody else, and its owner has not allowed this agent yet. Ask them to allow it in Zas.",
162
163
  not_found: "That item is not in the channel.",
163
164
  invalid_cap: "That file is no longer available.",
164
165
  write_failed: "The file could not be saved to the destination.",
@@ -187,6 +188,7 @@ var SENTENCES = {
187
188
  pairing_claimed: "This pairing was already claimed. Pair again with \u201Czas-agent pair\u201D.",
188
189
  agent_limit: "The account cannot take another agent. The owner deletes one from Settings \u2192 Agents.",
189
190
  grant_limit: "The plan allows fewer channels per agent than this pairing grants.",
191
+ foreign_limit: "The plan allows this agent fewer channels of other people than this pairing asks for.",
190
192
  feature_disabled: "Agents are not enabled for this account yet.",
191
193
  upload_failed: "The upload failed. Try again.",
192
194
  oprf_failed: "Zas did not answer correctly while preparing the file. Try again.",
@@ -1036,8 +1038,8 @@ function keepUrls(servers, keep) {
1036
1038
  const kept = [];
1037
1039
  for (const server of servers) {
1038
1040
  const urls = typeof server.urls === "string" ? [server.urls] : server.urls ?? [];
1039
- const usable = urls.filter(keep);
1040
- if (usable.length > 0) kept.push({ ...server, urls: usable });
1041
+ const usable2 = urls.filter(keep);
1042
+ if (usable2.length > 0) kept.push({ ...server, urls: usable2 });
1041
1043
  }
1042
1044
  return kept;
1043
1045
  }
@@ -2075,15 +2077,22 @@ function channelNameOf(identity, grant) {
2075
2077
  throw new ZasError("key_stale", 0);
2076
2078
  }
2077
2079
  }
2080
+ function channelAccountOf(identity, grant) {
2081
+ return grant.channel_owner_uid !== void 0 && grant.channel_owner_uid !== "" ? grant.channel_owner_uid : identity.owner_uid;
2082
+ }
2078
2083
  var fold = (value) => value.trim().toLowerCase();
2084
+ function usable(grant) {
2085
+ if (grant.pending) throw new ZasError("grant_pending", 0);
2086
+ return grant;
2087
+ }
2079
2088
  function resolveChannel(identity, grants, channel) {
2080
2089
  if (channel === void 0 || fold(channel) === "") {
2081
- if (grants.length === 1) return grants[0];
2090
+ if (grants.length === 1) return usable(grants[0]);
2082
2091
  throw new ZasError("grant_missing", 0);
2083
2092
  }
2084
2093
  const wanted = channel.trim();
2085
2094
  const byId = grants.find((g) => g.channel_id === wanted);
2086
- if (byId) return byId;
2095
+ if (byId) return usable(byId);
2087
2096
  const folded = fold(wanted);
2088
2097
  const byName = grants.filter((g) => {
2089
2098
  try {
@@ -2092,7 +2101,7 @@ function resolveChannel(identity, grants, channel) {
2092
2101
  return false;
2093
2102
  }
2094
2103
  });
2095
- if (byName.length === 1) return byName[0];
2104
+ if (byName.length === 1) return usable(byName[0]);
2096
2105
  throw new ZasError("grant_missing", 0);
2097
2106
  }
2098
2107
 
@@ -2784,7 +2793,7 @@ async function sendDirect(ctx, input, report2, deps = {}) {
2784
2793
  ...stamp
2785
2794
  });
2786
2795
  const route = `/direct/${cid}/${id}`;
2787
- const offerPath = `accounts/${owner}/channels/${cid}/direct/${id}`;
2796
+ const offerPath = `accounts/${channelAccountOf(ctx.identity, grant)}/channels/${cid}/direct/${id}`;
2788
2797
  const setState = (state) => ctx.client.api("POST", `${route}/state`, { state, ...stamp }).then(() => void 0, () => void 0);
2789
2798
  const claimBy = startedAt + (deps.offerWaitMs ?? OFFER_WAIT_MS);
2790
2799
  for (; ; ) {
@@ -3060,7 +3069,7 @@ function nameFrom(channelKey, grant) {
3060
3069
  }
3061
3070
  }
3062
3071
  function linksParent(identity, grant) {
3063
- return `accounts/${identity.owner_uid}/channels/${grant.channel_id}`;
3072
+ return `accounts/${channelAccountOf(identity, grant)}/channels/${grant.channel_id}`;
3064
3073
  }
3065
3074
  function linkPath(identity, grant, id) {
3066
3075
  return `projects/${identity.firestore_project}/databases/(default)/documents/${linksParent(identity, grant)}/links/${id}`;
@@ -3341,11 +3350,12 @@ async function receiveDirect(ctx, input, report2, deps = {}) {
3341
3350
  const channelName = channelLabel(ctx, grant);
3342
3351
  const cid = grant.channel_id;
3343
3352
  const owner = ctx.identity.owner_uid;
3353
+ const account = channelAccountOf(ctx.identity, grant);
3344
3354
  const device = deps.device ?? newDeviceToken();
3345
3355
  const { seal, open } = sealer(key, grant.key_version);
3346
3356
  const stamp = { device, owner_uid: owner };
3347
3357
  const startedAt = now();
3348
- const channelPath = `accounts/${owner}/channels/${cid}`;
3358
+ const channelPath = `accounts/${account}/channels/${cid}`;
3349
3359
  report2("waiting");
3350
3360
  const waitUntil = startedAt + (deps.offerWaitMs ?? OFFER_WAIT_MS2);
3351
3361
  let id;
@@ -3480,6 +3490,7 @@ async function receiveDirect(ctx, input, report2, deps = {}) {
3480
3490
  channel_name: channelName,
3481
3491
  offer_id: offerId,
3482
3492
  owner_uid: owner,
3493
+ channel_account: account,
3483
3494
  device,
3484
3495
  ...input.dest !== void 0 ? { dest: input.dest } : {},
3485
3496
  meta: offered,
@@ -3495,7 +3506,7 @@ async function receiveDirectFallback(ctx, record, report2, deps = {}) {
3495
3506
  const { open } = sealer(record.key, record.key_version);
3496
3507
  const stamp = { device: record.device, owner_uid: record.owner_uid };
3497
3508
  const base = `/direct/${record.channel_id}/${record.offer_id}/fallback`;
3498
- const offerPath = `accounts/${record.owner_uid}/channels/${record.channel_id}/direct/${record.offer_id}`;
3509
+ const offerPath = `accounts/${record.channel_account}/channels/${record.channel_id}/direct/${record.offer_id}`;
3499
3510
  const doc = await ctx.client.firestoreGet(offerPath);
3500
3511
  const enc = stringField2(doc, "fallback_meta_enc");
3501
3512
  let meta;
@@ -4088,7 +4099,7 @@ async function report(client, properties) {
4088
4099
 
4089
4100
  // src/server.ts
4090
4101
  function agentVersion() {
4091
- return true ? "0.7.0" : "0.0.0-dev";
4102
+ return true ? "0.8.0" : "0.0.0-dev";
4092
4103
  }
4093
4104
  var AGENT_CUE = " The owner sees every item this agent sends with the >_ agent mark and this agent's name, on every device.";
4094
4105
  var PAIR_ANNOUNCE_MS = 15e3;
@@ -4098,6 +4109,7 @@ var delay = (ms) => new Promise((resolve2) => {
4098
4109
  setTimeout(resolve2, ms);
4099
4110
  });
4100
4111
  function rightsOf(grant) {
4112
+ if (grant.pending) return "waiting for the channel\u2019s owner";
4101
4113
  const rights = [];
4102
4114
  if (grant.send && grant.mode !== "view") rights.push(grant.direct_mode ? "send (Directo)" : "send");
4103
4115
  if (grant.read) rights.push(grant.direct_mode ? "receive (Directo)" : "read");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zas-agent",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
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",