@awebai/pi 0.1.15 → 0.1.16

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/dist/index.js CHANGED
@@ -5904,6 +5904,42 @@ function escapeJSON3(s) {
5904
5904
  return result;
5905
5905
  }
5906
5906
 
5907
+ // ../channel-core/dist/local_aw.js
5908
+ import { execFile } from "node:child_process";
5909
+ import { promisify } from "node:util";
5910
+ var execFileAsync = promisify(execFile);
5911
+ function createLocalAWDecryptProvider(options) {
5912
+ const awCommand = options.awCommand || process.env.AW_BIN || "aw";
5913
+ return {
5914
+ async mailMessage(messageID) {
5915
+ const id = messageID.trim();
5916
+ if (!id)
5917
+ return null;
5918
+ const { stdout } = await execFileAsync(awCommand, ["mail", "show", "--message-id", id, "--json"], { cwd: options.workdir, timeout: 15e3, maxBuffer: 1024 * 1024 });
5919
+ const payload = parseJSONOutput(stdout);
5920
+ return (payload.messages || []).find((msg) => msg.message_id === id) || null;
5921
+ },
5922
+ async chatMessage(sessionID, messageID) {
5923
+ const session = sessionID.trim();
5924
+ const id = messageID.trim();
5925
+ if (!session || !id)
5926
+ return null;
5927
+ const { stdout } = await execFileAsync(awCommand, ["chat", "history", "--session-id", session, "--message-id", id, "--limit", "1", "--json"], { cwd: options.workdir, timeout: 15e3, maxBuffer: 1024 * 1024 });
5928
+ const payload = parseJSONOutput(stdout);
5929
+ return (payload.messages || []).find((msg) => msg.message_id === id) || null;
5930
+ }
5931
+ };
5932
+ }
5933
+ function parseJSONOutput(stdout) {
5934
+ const trimmed = stdout.trim();
5935
+ if (!trimmed)
5936
+ throw new Error("aw returned empty JSON output");
5937
+ const start = trimmed.indexOf("{");
5938
+ if (start < 0)
5939
+ throw new Error("aw JSON output did not contain an object");
5940
+ return JSON.parse(trimmed.slice(start));
5941
+ }
5942
+
5907
5943
  // ../channel-core/dist/channel.js
5908
5944
  import { dirname as dirname2, join as join2 } from "node:path";
5909
5945
  import { homedir } from "node:os";
@@ -6003,11 +6039,12 @@ function createChannelClient(config) {
6003
6039
  async function startChannelLoop(options) {
6004
6040
  const dispatched = /* @__PURE__ */ new Set();
6005
6041
  const deliveryStore = options.deliveryStore || await DeliveryStore.load(DEFAULT_DELIVERY_STORE_PATH);
6042
+ const localDecrypt = options.localDecrypt || (options.workdir ? createLocalAWDecryptProvider({ workdir: options.workdir, awCommand: options.awCommand }) : void 0);
6006
6043
  const log = options.log || (() => {
6007
6044
  });
6008
6045
  for await (const event of streamAgentEvents(options.client, options.signal)) {
6009
6046
  try {
6010
- await dispatchAgentEvent({ ...options, deliveryStore }, dispatched, event);
6047
+ await dispatchAgentEvent({ ...options, deliveryStore, localDecrypt }, dispatched, event);
6011
6048
  pruneDispatched(dispatched);
6012
6049
  } catch (err2) {
6013
6050
  log(`[aw-channel] dispatch error: ${err2}`);
@@ -6089,6 +6126,16 @@ async function dispatchMailEvent(options, dispatched, event) {
6089
6126
  continue;
6090
6127
  }
6091
6128
  const from = senderDisplayAddress(msg.from_alias, msg.from_address);
6129
+ const decrypt = await resolveMailForDelivery(options, msg);
6130
+ if (!decrypt.ok) {
6131
+ await options.onAwakening({
6132
+ kind: "mail",
6133
+ content: "",
6134
+ meta: encryptedDeliveryFailureMeta("mail", from, msg.message_id, conversationID, decrypt.error),
6135
+ deliveryIntent: "wake"
6136
+ });
6137
+ continue;
6138
+ }
6092
6139
  const trust = await normalizeMessageTrust(options, msg, msg.from_alias, msg.from_address, msg.to_did, msg.to_stable_id);
6093
6140
  msg.verification_status = trust.status;
6094
6141
  if (trust.stored)
@@ -6138,6 +6185,16 @@ async function dispatchChatEvent(options, dispatched, event) {
6138
6185
  continue;
6139
6186
  }
6140
6187
  const from = senderDisplayAddress(msg.from_agent, msg.from_address);
6188
+ const decrypt = await resolveChatForDelivery(options, event.session_id, msg);
6189
+ if (!decrypt.ok) {
6190
+ await options.onAwakening({
6191
+ kind: "chat",
6192
+ content: "",
6193
+ meta: encryptedDeliveryFailureMeta("chat", from, msg.message_id, conversationID, decrypt.error, event.session_id),
6194
+ deliveryIntent: event.sender_waiting ? "steer" : "wake"
6195
+ });
6196
+ continue;
6197
+ }
6141
6198
  const trust = await normalizeMessageTrust(options, msg, msg.from_agent, msg.from_address, msg.to_did, msg.to_stable_id);
6142
6199
  msg.verification_status = trust.status;
6143
6200
  if (trust.stored)
@@ -6177,6 +6234,58 @@ async function dispatchChatEvent(options, dispatched, event) {
6177
6234
  async function normalizeMessageTrust(options, msg, fromAlias, fromAddress, toDID, toStableID) {
6178
6235
  return options.trust.normalizeTrust(options.pinStore, msg.verification_status, senderTrustAddress(fromAlias, fromAddress), msg.from_did, msg.from_stable_id, toDID, toStableID, msg.rotation_announcement, msg.replacement_announcement, msg.signed_from || fromAddress || fromAlias || "");
6179
6236
  }
6237
+ async function resolveMailForDelivery(options, msg) {
6238
+ if (!isEncryptedMessage(msg))
6239
+ return { ok: true };
6240
+ if (!options.localDecrypt?.mailMessage) {
6241
+ return { ok: false, error: "local aw decrypt provider is not configured" };
6242
+ }
6243
+ try {
6244
+ const decrypted = await options.localDecrypt.mailMessage(msg.message_id);
6245
+ if (!decrypted || typeof decrypted.body !== "string") {
6246
+ return { ok: false, error: "local aw did not return decrypted mail body" };
6247
+ }
6248
+ Object.assign(msg, decrypted);
6249
+ return { ok: true };
6250
+ } catch (error) {
6251
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
6252
+ }
6253
+ }
6254
+ async function resolveChatForDelivery(options, sessionID, msg) {
6255
+ if (!isEncryptedMessage(msg))
6256
+ return { ok: true };
6257
+ if (!options.localDecrypt?.chatMessage) {
6258
+ return { ok: false, error: "local aw decrypt provider is not configured" };
6259
+ }
6260
+ try {
6261
+ const decrypted = await options.localDecrypt.chatMessage(sessionID, msg.message_id);
6262
+ if (!decrypted || typeof decrypted.body !== "string") {
6263
+ return { ok: false, error: "local aw did not return decrypted chat body" };
6264
+ }
6265
+ Object.assign(msg, decrypted);
6266
+ return { ok: true };
6267
+ } catch (error) {
6268
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
6269
+ }
6270
+ }
6271
+ function isEncryptedMessage(msg) {
6272
+ return msg.content_mode === "encrypted_v2" || msg.message_version === 2 || msg.encrypted_envelope !== void 0;
6273
+ }
6274
+ function encryptedDeliveryFailureMeta(type2, from, messageID, conversationID, error, sessionID) {
6275
+ const meta = {
6276
+ type: type2,
6277
+ from,
6278
+ message_id: messageID,
6279
+ encrypted: "true",
6280
+ decrypted: "false",
6281
+ decrypt_error: error
6282
+ };
6283
+ if (conversationID)
6284
+ meta.conversation_id = conversationID;
6285
+ if (sessionID)
6286
+ meta.session_id = sessionID;
6287
+ return meta;
6288
+ }
6180
6289
  function isTrustedVerificationStatus(status) {
6181
6290
  return status === "verified" || status === "verified_custodial";
6182
6291
  }
@@ -6486,6 +6595,7 @@ ${message}`),
6486
6595
  stableID: config.stableID
6487
6596
  },
6488
6597
  signal,
6598
+ workdir: ctx.cwd,
6489
6599
  onAwakening: (awakening) => sendAwakening(pi, awakening),
6490
6600
  log: (message) => {
6491
6601
  if (ctx.hasUI) ctx.ui.notify(message, "warning");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awebai/pi",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "Aweb for Pi: real-time channel awakenings, aw CLI onboarding, and aweb skills.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -67,7 +67,7 @@ The normative E2E contract is `docs/e2e-messaging-contract.md`. Do not invent pr
67
67
 
68
68
  For local E2E messaging, the self-custodial client needs both identity signing material and local encryption private keys. Back up the active encryption private key and archived encryption private keys with the same seriousness as `.aw/signing.key`: losing archived encryption keys makes historical encrypted messages unrecoverable. AC/aweb cannot recover or decrypt old encrypted messages for support.
69
69
 
70
- An identity must publish an identity-signed encryption-key assertion before it can receive E2E messages. Missing, stale, unsigned, or mismatched encryption-key discovery fails closed; do not retry as plaintext unless the human explicitly chooses the separately named legacy plaintext mode from the final CLI. Service signatures may assert route support, but not recipient encryption-key authority. Local identities omit absent `stable_id`/address fields instead of sending empty strings. In non-interactive runs, stop, report the exact `aw doctor` / command error, and ask the human or coordinator to run the approved key setup, backup, or rotation flow.
70
+ An identity must publish an identity-signed encryption-key assertion before it can receive E2E messages. New self-custodial identity and team-install paths create local encryption key material automatically, including `aw id create`, `aw init`, `aw service init`, `aw id team accept-invite`, `aw id team fetch-cert`, and bootstrap/add-worktree flows. Current aw also creates/publishes the sender's key on the first default-E2E send from an upgraded old worktree. It cannot create keys for a different old recipient; if the recipient has no published key, tell them to upgrade aw/Pi/channel and run `aw id encryption-key setup`, or ask the human whether to send a server-readable upgrade note with `--plaintext`. Missing, stale, unsigned, or mismatched encryption-key discovery fails closed; do not retry as plaintext unless the human explicitly chooses `--plaintext`. Service signatures may assert route support, but not recipient encryption-key authority. Local identities omit absent `stable_id`/address fields instead of sending empty strings. In non-interactive runs, stop, report the exact `aw doctor` / command error, and ask the human or coordinator to run the approved key setup, backup, or rotation flow.
71
71
 
72
72
  Use the CLI keyring commands for self-custodial E2E readiness:
73
73
 
@@ -161,7 +161,7 @@ Interpret failures by what's missing (file references assume a self-custodial CL
161
161
 
162
162
  - **No `.aw/` in this directory** — there is no workspace here at all. Run `aw init` or move to a directory that has been initialized.
163
163
  - **`.aw/signing.key` missing** — workspace exists but has no signing key. Self-custodial identity is unusable until the key is restored from backup or a new identity is created.
164
- - **E2E encryption-key check fails** — distinguish the cases the CLI reports: missing local encryption private key, missing published encryption-key assertion, stale/mismatched assertion, or missing archived key for an older message. Do not advise plaintext fallback. Capture the exact error, run `aw doctor` when available, and ask the human to restore keys from backup or run `aw id encryption-key setup` / `aw id encryption-key rotate` as appropriate.
164
+ - **E2E encryption-key check fails** — distinguish the cases the CLI reports: missing local encryption private key, missing published encryption-key assertion, stale/mismatched assertion, or missing archived key for an older message. Do not advise plaintext fallback. Capture the exact error, run `aw doctor` when available, and ask the human to restore keys from backup or run `aw id encryption-key setup` / `aw id encryption-key rotate` as appropriate. Use `--plaintext` only when the human explicitly chooses server-readable plaintext.
165
165
  - **`.aw/workspace.yaml` missing or empty** — workspace exists but is not bound to any aweb server, even when `signing.key` is present. Re-run `aw init`.
166
166
  - **No global identity / no `did:aw` registered** — only a local workspace identity exists. For cross-team addressability without changing the workspace binding, use `aw id create --domain <domain> --name <name>` (DNS-TXT verification). Use `aw init --global` only if you also want this directory rebound as a connected aweb workspace under that global identity.
167
167
  - **Already ran `aw init --byod --global` expecting offline BYOT prep** — that command bootstrapped and connected this directory to the `default:<domain>` team on app.aweb.ai (the team created during BYOD onboarding), leaving `.aw/{identity.yaml,signing.key,teams.yaml,workspace.yaml,team-certs/}` populated. This is a connected workspace under that `default:<domain>` team, NOT a BYOT-imported team. To recover, pick one:
@@ -12,7 +12,9 @@ It also covers explicit user requests to send mail or chat through aweb.
12
12
 
13
13
  E2E messaging boundary: for encrypted v2 messages, AC/aweb servers route ciphertext and local clients decrypt before showing or injecting plaintext. Hosted custodial MCP, dashboard-side send/read, and server-side tools are **server-readable hosted messaging**, not E2E. Do not describe hosted custodial/server-side messaging as end-to-end encrypted.
14
14
 
15
- Legacy plaintext boundary: existing plaintext mail/chat remains legacy/server-readable while retained and must not be described as retroactively E2E. If an intended E2E send fails because keys, capability, route support, or version support is missing/stale/mismatched, stop and report the exact failure. Do not resend as plaintext unless the human explicitly chooses the approved legacy plaintext command/flag.
15
+ Legacy plaintext boundary: existing plaintext mail/chat remains legacy/server-readable while retained and must not be described as retroactively E2E. Mail and chat send E2E by default. If an intended E2E send fails because keys, capability, route support, or version support is missing/stale/mismatched, stop and report the exact failure. Do not resend as plaintext unless the human explicitly chooses `--plaintext`.
16
+
17
+ Mixed-version boundary: old pre-E2E aw/channel/Pi clients may not have published recipient encryption keys yet. A current aw CLI creates/publishes the sender's local encryption key before default-E2E sends, but it cannot create keys for another recipient. If the recipient lacks a key, report that they need to upgrade aw/Pi/channel and publish a key. Use `--plaintext` only for an explicit human-approved server-readable upgrade note.
16
18
 
17
19
  If the event says to use the aw CLI and the response is not obvious, continue with this skill. For broader work coordination, load `aweb-coordination`. For recipient addressability, inbound-mode policy, team membership, or multi-team identity questions, load `aweb-team-membership`.
18
20
 
@@ -77,15 +79,15 @@ aw chat extend-wait <from> "working on it, 2 minutes"
77
79
 
78
80
  Before replying to a confusing chat, inspect pending/open/history state. Do not use chat for broad FYI updates. Send mail instead.
79
81
 
80
- Encrypted chat is explicit. Use `--e2ee` only when the human or policy asks for E2E chat and every participant has identity-authorized encryption capability:
82
+ Mail and chat are E2E by default. Do not add `--e2ee`; it is a deprecated compatibility no-op. Use `--plaintext` only when the human explicitly asks for server-readable plaintext and policy allows it.
81
83
 
82
84
  ```bash
83
- aw chat send-and-wait <alias-or-address> "..." --start-conversation --e2ee
84
- aw chat send-and-leave <alias-or-address> "..." --e2ee
85
- aw chat extend-wait <from> "working on it" --e2ee
85
+ aw chat send-and-wait <alias-or-address> "..." --start-conversation
86
+ aw chat send-and-leave <alias-or-address> "..."
87
+ aw chat extend-wait <from> "working on it"
86
88
  ```
87
89
 
88
- Read paths such as `aw chat pending`, `aw chat history`, and channel listen/decrypt paths show plaintext only after local decryption. If `--e2ee` fails because a key, capability, route, or version is missing or stale, stop and report the exact error; do not resend as plaintext unless the human explicitly chooses the approved legacy plaintext path.
90
+ Read paths such as `aw chat pending`, `aw chat history`, and channel listen/decrypt paths show plaintext only after local decryption. If the encrypted send fails because a key, capability, route, or version is missing or stale, stop and report the exact error; do not resend as plaintext unless the human explicitly chooses `--plaintext`.
89
91
 
90
92
  ## Harness surfaces
91
93
 
@@ -109,7 +111,7 @@ Harness support differs:
109
111
 
110
112
  The channel is inbound only. Use `aw mail` or `aw chat` to respond.
111
113
 
112
- For E2E messages, channel/Pi/`aw run` may show plaintext only after local decryption in the user's workspace or client process. Server notifications and SSE payloads should be metadata-only for encrypted content. Recipient E2E capability and encryption keys must be identity-authorized; a service signature can assert route support only. If an event or tool error says an encryption key/capability is missing, stale, or mismatched, fail closed and report the error. Do not silently resend as plaintext.
114
+ For E2E messages, channel/Pi/`aw run` may show plaintext only after local decryption in the user's workspace or client process. Server notifications and SSE payloads should be metadata-only for encrypted content. Recipient E2E capability and encryption keys must be identity-authorized; a service signature can assert route support only. If an event or tool error says an encryption key/capability is missing, stale, or mismatched, fail closed and report the error. Do not silently resend as plaintext. If the local channel/Pi process was upgraded from a pre-E2E version, run `aw id encryption-key setup` in the workspace before expecting it to receive encrypted messages.
113
115
 
114
116
  ## Control and work awakenings
115
117