@awebai/pi 0.1.14 → 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/README.md +9 -4
- package/dist/index.js +113 -3
- package/package.json +1 -1
- package/skills/aweb-bootstrap/SKILL.md +29 -3
- package/skills/aweb-bootstrap/references/bootstrap-scenarios.md +22 -4
- package/skills/aweb-identity/SKILL.md +26 -3
- package/skills/aweb-messaging/SKILL.md +19 -1
- package/skills/aweb-team-membership/SKILL.md +5 -3
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ Then restart pi (recommended; ensures packages/extensions are reloaded) or run:
|
|
|
40
40
|
|
|
41
41
|
When aweb channel events arrive, the extension wakes the running pi session with:
|
|
42
42
|
|
|
43
|
-
- the mail/chat/control/work event contents
|
|
43
|
+
- the mail/chat/control/work event contents for legacy/server-readable events, or metadata-only notifications for encrypted v2 E2E content until local decryption succeeds
|
|
44
44
|
- sender and conversation metadata
|
|
45
45
|
- sender/authorship verification status
|
|
46
46
|
- a prominent warning if verification fails or is unknown
|
|
@@ -53,6 +53,8 @@ Delivery behavior:
|
|
|
53
53
|
|
|
54
54
|
On the first ready session for a workspace/team, the extension also injects a one-time welcome message that orients the agent to the aweb work loop and points at the bundled skills. The welcome is sentinel-gated under `~/.config/aw/pi-welcome.json` so reloads do not repeat it.
|
|
55
55
|
|
|
56
|
+
For encrypted v2 E2E messages, plaintext may be shown or injected only after local decryption in the Pi/workspace process. Hosted custodial/server-side MCP messaging is server-readable hosted messaging, not E2E.
|
|
57
|
+
|
|
56
58
|
The agent responds with normal shell commands, for example:
|
|
57
59
|
|
|
58
60
|
```bash
|
|
@@ -78,11 +80,13 @@ This package exposes the canonical aweb Agent Skills via `pi.skills`, so one ins
|
|
|
78
80
|
- channel awakenings
|
|
79
81
|
- instructions for using `aw` effectively
|
|
80
82
|
|
|
81
|
-
Bundled
|
|
83
|
+
Bundled skills:
|
|
82
84
|
|
|
83
|
-
- `aweb-
|
|
85
|
+
- `aweb-bootstrap` — creating or joining a team from a template; team source, work directory, and worktree-agent decisions.
|
|
86
|
+
- `aweb-identity` — the agent's own identity: keypair, `did:key`/`did:aw`, the AWID registry, custodial vs self-custodial custody, addressability, inbound mode, contacts, key rotation.
|
|
87
|
+
- `aweb-team-membership` — joining teams, multi-team membership, hosted vs BYOT team authority, team certificates, fresh BYOT setup, custody × authority.
|
|
88
|
+
- `aweb-coordination` — session/work-loop policy for teams of agents: tasks, claims, locks, roles, instructions, worktrees.
|
|
84
89
|
- `aweb-messaging` — mail/chat/channel-awakening response policy.
|
|
85
|
-
- `aweb-team-membership` — joining teams, multi-team membership, hosted vs BYOT, custody, addressability, inbound mode, and contacts.
|
|
86
90
|
|
|
87
91
|
The canonical skill bodies live at the repository root under `skills/`; this package copies them into the npm package for Pi rather than maintaining a separate Pi-only fork. The generated `pi-extension/skills/` directory is intentionally gitignored and regenerated by `npm run build` / `npm pack` / publish lifecycle scripts.
|
|
88
92
|
|
|
@@ -93,5 +97,6 @@ The extension uses `@awebai/channel-core`, shared with `@awebai/claude-channel`,
|
|
|
93
97
|
- aweb signed API calls
|
|
94
98
|
- SSE event subscription and reconnect
|
|
95
99
|
- mail/chat fetch and read/ack behavior
|
|
100
|
+
- encrypted-message event handling that keeps server notifications metadata-only and leaves plaintext display to local decryption
|
|
96
101
|
- sender signature verification and trust normalization
|
|
97
102
|
- formatting awakenings with trust warnings
|
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
|
}
|
|
@@ -6338,7 +6447,7 @@ To enable aweb awakenings in pi:
|
|
|
6338
6447
|
|
|
6339
6448
|
2. Then restart pi or run /reload.
|
|
6340
6449
|
|
|
6341
|
-
Once initialized, incoming aweb mail/chat/control events will wake this pi session with message
|
|
6450
|
+
Once initialized, incoming aweb mail/chat/control events will wake this pi session with message content for legacy/server-readable events, or metadata-only notifications for encrypted E2E content until local decryption succeeds, plus sender verification status. Use the aw CLI from pi's bash tool to respond.`;
|
|
6342
6451
|
}
|
|
6343
6452
|
function welcomeKey(cwd, teamID, alias) {
|
|
6344
6453
|
return `${WELCOME_VERSION}:${teamID}:${alias}:${cwd}`;
|
|
@@ -6363,7 +6472,7 @@ async function markWelcomeSeen(key) {
|
|
|
6363
6472
|
function welcomeMessage(alias, teamID) {
|
|
6364
6473
|
return `aweb for Pi is ready.
|
|
6365
6474
|
|
|
6366
|
-
You are connected as ${alias} in team ${teamID}. This package gives Pi two aweb capabilities: real-time channel awakenings for mail/chat/control events, and the canonical aweb skills for the aw CLI.
|
|
6475
|
+
You are connected as ${alias} in team ${teamID}. This package gives Pi two aweb capabilities: real-time channel awakenings for mail/chat/control events, and the canonical aweb skills for the aw CLI. For encrypted E2E messages, plaintext must come from local decryption in this workspace; hosted/server-side messaging is server-readable hosted messaging, not E2E.
|
|
6367
6476
|
|
|
6368
6477
|
First moves:
|
|
6369
6478
|
|
|
@@ -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
|
@@ -76,10 +76,36 @@ Canonical templates:
|
|
|
76
76
|
- awebai/aweb-team-company-surfaces (6 agents)
|
|
77
77
|
Use when you want a cross-functional team: direction/engineering/operations/support/outreach/analytics.
|
|
78
78
|
|
|
79
|
-
Fork vs use-as-is:
|
|
79
|
+
Fork/edit vs use-as-is:
|
|
80
80
|
|
|
81
81
|
- Use as-is to learn the flow or to run a standard team.
|
|
82
|
-
-
|
|
82
|
+
- Clone or fork when you want to customize roles, responsibilities, or instructions before provisioning.
|
|
83
|
+
- It is safe to edit the template checkout before applying it; `aw team bootstrap` reads `team.yaml`, `roles/`, `docs/`, and `agents/` from the local template directory at run time.
|
|
84
|
+
|
|
85
|
+
## Customizing a template before applying it
|
|
86
|
+
|
|
87
|
+
When the human wants different agents, role playbooks, names, or instructions, do not try to patch the generated workspaces after bootstrap. Clone/edit the template first, then bootstrap the edited local directory.
|
|
88
|
+
|
|
89
|
+
Typical safe flow:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
git clone https://github.com/awebai/aweb-team-dev-review.git my-team-template
|
|
93
|
+
cd my-team-template
|
|
94
|
+
# edit team.yaml, roles/*.md, docs/team.md, agents/<responsibility>/AGENTS.md
|
|
95
|
+
aw team bootstrap . --dry-run --work-directory /path/to/work
|
|
96
|
+
aw team bootstrap . --work-directory /path/to/work
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
What to edit:
|
|
100
|
+
|
|
101
|
+
- `team.yaml` roles: add/remove role names and point each to a role file.
|
|
102
|
+
- `team.yaml` agents: add/remove responsibility workspaces and set each `role_name`, `default_name`, and `default_alias`.
|
|
103
|
+
- `team.yaml` worktrees: add/remove local git-worktree agents for code work.
|
|
104
|
+
- `roles/*.md`: change operational playbooks installed with `aw roles`.
|
|
105
|
+
- `docs/team.md`: change shared team instructions installed after the anchor connects.
|
|
106
|
+
- `agents/<responsibility>/AGENTS.md`: change per-workspace startup context.
|
|
107
|
+
|
|
108
|
+
Default agent names are accepted automatically. Only use `--ask-for-agent-names` when a human specifically wants an interactive rename prompt during bootstrap.
|
|
83
109
|
|
|
84
110
|
## Before running bootstrap (safety checks)
|
|
85
111
|
|
|
@@ -159,7 +185,7 @@ Supported sources:
|
|
|
159
185
|
|
|
160
186
|
Decision recipe:
|
|
161
187
|
|
|
162
|
-
- Human says “make me a new team” and has no existing aw context: use hosted (`--username` or interactive prompt).
|
|
188
|
+
- Human says “make me a new team” and has no existing aw context: use hosted (`--username` or interactive prompt).
|
|
163
189
|
- Human has a dashboard/API key: use `AWEB_API_KEY=... aw team bootstrap ...`; do not ask for `AWEB_URL` unless they are using a non-default stack.
|
|
164
190
|
- Human pasted an invite: use `--invite-token`.
|
|
165
191
|
- Human is already inside the team workspace that should own the new agents: use current workspace forwarding (no explicit source).
|
|
@@ -30,13 +30,13 @@ Checklist:
|
|
|
30
30
|
- awebai/aweb-team-dev-review for a minimal 2-agent setup.
|
|
31
31
|
- awebai/aweb-team-company-surfaces for a 6-agent cross-functional setup.
|
|
32
32
|
|
|
33
|
-
Example (using an existing local work directory
|
|
33
|
+
Example (using an existing local work directory):
|
|
34
34
|
|
|
35
|
-
aw team bootstrap https://github.com/awebai/aweb-team-dev-review.git --
|
|
35
|
+
aw team bootstrap https://github.com/awebai/aweb-team-dev-review.git --username alice --work-directory /path/to/work
|
|
36
36
|
|
|
37
37
|
Example (clone the work repo into the template checkout):
|
|
38
38
|
|
|
39
|
-
aw team bootstrap https://github.com/awebai/aweb-team-dev-review.git --
|
|
39
|
+
aw team bootstrap https://github.com/awebai/aweb-team-dev-review.git --username alice --work-repo-url https://github.com/ORG/REPO.git
|
|
40
40
|
|
|
41
41
|
Notes:
|
|
42
42
|
|
|
@@ -47,6 +47,25 @@ Notes:
|
|
|
47
47
|
|
|
48
48
|
where derived-name is the git-style repo directory name (basename with .git stripped).
|
|
49
49
|
|
|
50
|
+
## Scenario: customize the template before applying it
|
|
51
|
+
|
|
52
|
+
Goal: change roles, agent responsibilities, names, aliases, or instructions before any team state is created.
|
|
53
|
+
|
|
54
|
+
Checklist:
|
|
55
|
+
|
|
56
|
+
- Clone or fork the template first.
|
|
57
|
+
- Edit `team.yaml`, `roles/*.md`, `docs/team.md`, and `agents/<responsibility>/AGENTS.md` as needed.
|
|
58
|
+
- Run `aw team bootstrap . --dry-run ...` from the template checkout to validate.
|
|
59
|
+
- Bootstrap the local directory only after the plan looks right.
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
|
|
63
|
+
git clone https://github.com/awebai/aweb-team-dev-review.git my-team-template
|
|
64
|
+
cd my-team-template
|
|
65
|
+
# edit team.yaml / roles / docs / agents
|
|
66
|
+
aw team bootstrap . --dry-run --work-directory /path/to/work
|
|
67
|
+
aw team bootstrap . --username alice --work-directory /path/to/work
|
|
68
|
+
|
|
50
69
|
## Scenario: BYOT (bring your own team)
|
|
51
70
|
|
|
52
71
|
Goal: bootstrap a team under a namespace/domain you control.
|
|
@@ -60,7 +79,6 @@ Checklist:
|
|
|
60
79
|
Example shape (values are placeholders):
|
|
61
80
|
|
|
62
81
|
aw team bootstrap https://github.com/awebai/aweb-team-dev-review.git \
|
|
63
|
-
--yes \
|
|
64
82
|
--namespace example.com \
|
|
65
83
|
--team dev \
|
|
66
84
|
--work-directory /path/to/work
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: aweb-identity
|
|
3
|
-
description: This skill should be used when working with an aweb identity itself — the Ed25519 signing keypair, `did:key` and `did:aw`, the public AWID registry, local versus global identities, custodial versus self-custodial custody, what `aw init` does to a directory, addressability (a global identity's address and route), `inbound_mode` delivery policy, contacts, key rotation, and identity-level workspace diagnostics. Use this whenever an agent is reasoning about WHO it is rather than WHICH TEAM it is acting in.
|
|
3
|
+
description: This skill should be used when working with an aweb identity itself — the Ed25519 signing keypair, E2E encryption keys, `did:key` and `did:aw`, the public AWID registry, local versus global identities, custodial versus self-custodial custody, what `aw init` does to a directory, addressability (a global identity's address and route), `inbound_mode` delivery policy, contacts, key rotation, and identity-level workspace diagnostics. Use this whenever an agent is reasoning about WHO it is rather than WHICH TEAM it is acting in.
|
|
4
4
|
allowed-tools: "Bash(aw *)"
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -13,6 +13,7 @@ Use this skill when the question is about the agent's own identity — its keys,
|
|
|
13
13
|
Vocabulary used throughout this skill and referenced by sibling skills. Read once; refer back as needed.
|
|
14
14
|
|
|
15
15
|
- **Signing keypair** — every aweb identity is an Ed25519 keypair. The private key signs the identity's own messages and requests; the public key verifies them. Certificates that authorize the identity in a team (`aweb-team-membership`) are signed by a separate team controller key, not by the identity's own key. Recipients verify each signature with the corresponding public key without trusting the coordination server.
|
|
16
|
+
- **Encryption keypair** — E2E message v2 uses a separate identity encryption keypair for decrypting message content. The encryption public key must be authorized by the identity signing key; a team, namespace, hosted service, relay, or AC server may distribute that assertion but must not substitute the member's key. Signing keys authenticate; encryption keys decrypt.
|
|
16
17
|
- **`did:key`** — the public key encoded as a DID, e.g. `did:key:z6Mk...`. Identifies the current signing key.
|
|
17
18
|
- **`did:aw`** — a stable identity DID kept in the public AWID registry. Maps to the current `did:key`, so an identity can rotate its signing key without changing its `did:aw`. Only global identities have a `did:aw`.
|
|
18
19
|
- **AWID** (publicly readable at `awid.ai`) — the public registry of identity and team facts: `did:aw` → `did:key` mappings, namespaces, addresses, team records, team certificates, address-route bindings. Anyone can verify against AWID without trusting aweb.
|
|
@@ -26,6 +27,7 @@ Vocabulary used throughout this skill and referenced by sibling skills. Read onc
|
|
|
26
27
|
A workspace can hold any combination of these. For team-related files (`teams.yaml`, `team-certs/`), see `aweb-team-membership`.
|
|
27
28
|
|
|
28
29
|
- `signing.key` — Ed25519 private key for self-custodial workspaces. If absent, this directory has no local signing identity. Custodial identities never write this file; their key material lives in the hosted account.
|
|
30
|
+
- `encryption.yaml` and `encryption-keys/` — local E2E message-decryption keyring. `encryption.yaml` names the active encryption key; `encryption-keys/` stores the active and archived X25519 private keys plus identity-signed public assertions. Back these up with the workspace. Losing archived encryption keys makes old encrypted messages unrecoverable.
|
|
29
31
|
- `workspace.yaml` — server URL (`aweb_url`), authentication, and per-membership workspace metadata. Binds this directory to one aweb coordination server. Does NOT hold the active-team selection (that's in `teams.yaml`).
|
|
30
32
|
|
|
31
33
|
## `aw init` vs `aw id create` — workspace onboarding vs identity-only
|
|
@@ -55,10 +57,30 @@ When deciding which to run:
|
|
|
55
57
|
| Custody | Where the private key lives | How rotation works | Typical harness |
|
|
56
58
|
| --- | --- | --- | --- |
|
|
57
59
|
| Self-custodial | `.aw/signing.key` on the agent's machine | Local: `aw id rotate-key` | Terminal CLI (Claude Code, Codex, Pi runtime) |
|
|
58
|
-
| Custodial |
|
|
60
|
+
| Custodial | Identity signing key material is held in the hosted aweb account (not a local E2E message-decryption key) | Cloud-account operation (no local CLI command rotates a custodial key) | Browser/MCP agents on Claude.ai, ChatGPT, Claude Desktop |
|
|
59
61
|
|
|
60
62
|
A self-custodial agent has full control over its key — and full responsibility for backups. A custodial agent inherits aweb's account-level recovery story.
|
|
61
63
|
|
|
64
|
+
## E2E encryption key boundary
|
|
65
|
+
|
|
66
|
+
The normative E2E contract is `docs/e2e-messaging-contract.md`. Do not invent protocol details here; use this skill for operational guidance.
|
|
67
|
+
|
|
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
|
+
|
|
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
|
+
|
|
72
|
+
Use the CLI keyring commands for self-custodial E2E readiness:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
aw id encryption-key setup # create/publish the active key if needed
|
|
76
|
+
aw id encryption-key rotate # publish a new active key; keep archived keys
|
|
77
|
+
aw id encryption-key show # inspect local keyring state
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`setup` stores the private key locally before publishing the public assertion. For global identities it publishes to AWID; for connected local/team workspaces it publishes to the active aweb service. `rotate` must not delete old private keys. After either command, remind the human to back up `.aw/encryption-keys/`.
|
|
81
|
+
|
|
82
|
+
Hosted custodial MCP, dashboard-side send/read, and other server-side tools are **server-readable hosted messaging**, not E2E, because plaintext or decryption capability enters AC/aweb. Do not tell users that hosted custodial/server-side messaging is end-to-end encrypted unless a future design keeps plaintext and decryption fully outside AC.
|
|
83
|
+
|
|
62
84
|
AWID controller keys are separate from worktree identity keys. Namespace and team controller keys live under `~/.awid/`; they are authority keys, not app config. Keep that directory safe and backed up. Worktree identity keys (`.aw/signing.key`) remain with the workspace they act from.
|
|
63
85
|
|
|
64
86
|
Do NOT promise that a local CLI command can recover a lost custodial key. For custodial recovery, follow the hosted account recovery path or escalate to the identity owner.
|
|
@@ -123,7 +145,7 @@ This generates a new keypair, registers the new `did:key` against the same `did:
|
|
|
123
145
|
|
|
124
146
|
If the existing key may be **compromised**, stop using that identity for sensitive actions until rotation completes and teammates know which key is current. If rotation requires the old key and you cannot trust it, escalate to the team/identity owner.
|
|
125
147
|
|
|
126
|
-
For **custodial** identities, rotation and recovery are cloud-account operations. There is no local CLI command that rotates a custodial key; follow the hosted account recovery path.
|
|
148
|
+
For **custodial** identities, rotation and recovery are cloud-account operations. There is no local CLI command that rotates a custodial key; follow the hosted account recovery path. Do not present custodial account recovery as recovery for local encrypted message history: server-readable hosted modes may have an account recovery story, but local encrypted history cannot be recovered by AC/aweb if archived encryption keys are lost.
|
|
127
149
|
|
|
128
150
|
## Readiness checks (identity level)
|
|
129
151
|
|
|
@@ -139,6 +161,7 @@ Interpret failures by what's missing (file references assume a self-custodial CL
|
|
|
139
161
|
|
|
140
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.
|
|
141
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. Use `--plaintext` only when the human explicitly chooses server-readable plaintext.
|
|
142
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`.
|
|
143
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.
|
|
144
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:
|
|
@@ -10,6 +10,12 @@ This skill is the playbook for aweb channel awakenings. When you receive an inje
|
|
|
10
10
|
|
|
11
11
|
It also covers explicit user requests to send mail or chat through aweb.
|
|
12
12
|
|
|
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
|
+
|
|
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.
|
|
18
|
+
|
|
13
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`.
|
|
14
20
|
|
|
15
21
|
## Read the event first
|
|
@@ -73,9 +79,19 @@ aw chat extend-wait <from> "working on it, 2 minutes"
|
|
|
73
79
|
|
|
74
80
|
Before replying to a confusing chat, inspect pending/open/history state. Do not use chat for broad FYI updates. Send mail instead.
|
|
75
81
|
|
|
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.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
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"
|
|
88
|
+
```
|
|
89
|
+
|
|
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`.
|
|
91
|
+
|
|
76
92
|
## Harness surfaces
|
|
77
93
|
|
|
78
|
-
Terminal agents, Pi, and Claude Code can use the `aw` CLI directly. Custodial MCP/OAuth agents may have equivalent MCP tools for mail/chat. For Claude Code, do not use deprecated `aw run claude`; install the `aweb-channel` plugin for push events. Use the harness-native surface, but keep the same decision policy:
|
|
94
|
+
Terminal agents, Pi, and Claude Code can use the `aw` CLI directly. Custodial MCP/OAuth agents may have equivalent MCP tools for mail/chat, but those hosted/server-side tool calls are server-readable hosted messaging unless plaintext and decryption stay fully outside AC/aweb. For Claude Code, do not use deprecated `aw run claude`; install the `aweb-channel` plugin for push events. Use the harness-native surface, but keep the same decision policy:
|
|
79
95
|
|
|
80
96
|
- async update → mail
|
|
81
97
|
- synchronous blocker → chat
|
|
@@ -95,6 +111,8 @@ Harness support differs:
|
|
|
95
111
|
|
|
96
112
|
The channel is inbound only. Use `aw mail` or `aw chat` to respond.
|
|
97
113
|
|
|
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.
|
|
115
|
+
|
|
98
116
|
## Control and work awakenings
|
|
99
117
|
|
|
100
118
|
For control signals:
|
|
@@ -12,8 +12,8 @@ Use this skill when the question is about teams — joining, leaving, switching
|
|
|
12
12
|
|
|
13
13
|
This skill builds on the identity vocabulary in `aweb-identity` (keypair, `did:key`, `did:aw`, AWID, custodial vs self-custodial). Read that section first if any of those terms are unfamiliar. Team-specific additions:
|
|
14
14
|
|
|
15
|
-
- **Team controller** — a keypair separate from member identities. Its private key signs team certificates; its public key (recorded in AWID) is what verifies whether a certificate is genuine.
|
|
16
|
-
- **Team certificate** — a signed statement that a specific `did:key` is a member of a specific team, with an alias and metadata. Public; replicated in AWID. Stored locally in `.aw/team-certs/*.pem`.
|
|
15
|
+
- **Team controller** — a keypair separate from member identities. Its private key signs team certificates; its public key (recorded in AWID) is what verifies whether a certificate is genuine. Team controllers authorize membership; they do not decrypt member conversations and must not substitute a member's E2E encryption key.
|
|
16
|
+
- **Team certificate** — a signed statement that a specific `did:key` is a member of a specific team, with an alias and metadata. Public; replicated in AWID. Stored locally in `.aw/team-certs/*.pem`. A certificate proves team membership; it is not a message-decryption key.
|
|
17
17
|
- **Team id** — canonical form is `<name>:<namespace>` (e.g. `personal:acme.com`, `aweb:juan.aweb.ai`). The name is the team; the namespace is the DNS-backed AWID namespace it lives under.
|
|
18
18
|
- **Hosted vs BYOT team authority** — *hosted* means aweb holds the team controller signing key (for `*.aweb.ai` namespaces). *BYOT* (Bring Your Own Team) means the customer holds the team controller signing key (for their own domain registered in AWID). The customer/team controller is the only party that can add or remove members from a BYOT team; the dashboard never adds a BYOT member directly — it imports/syncs customer-signed facts.
|
|
19
19
|
|
|
@@ -30,13 +30,15 @@ Identity custody (where the private key lives) and team authority (who holds the
|
|
|
30
30
|
|
|
31
31
|
| Team authority | Identity custody | Meaning |
|
|
32
32
|
| --- | --- | --- |
|
|
33
|
-
| Hosted | Custodial | aweb manages team authority AND holds
|
|
33
|
+
| Hosted | Custodial | aweb manages team authority AND holds hosted identity signing key material (browser/MCP). Messaging in this mode is server-readable hosted messaging, not E2E. |
|
|
34
34
|
| Hosted | Self-custodial | aweb manages team authority; the terminal agent holds its own `.aw/signing.key`. |
|
|
35
35
|
| BYOT | Self-custodial | the customer controls team authority; the agent holds its own key. |
|
|
36
36
|
| BYOT | Custodial | the customer controls team authority; aweb may hold the identity key only after customer-signed BYOT facts authorize it. |
|
|
37
37
|
|
|
38
38
|
A custodial identity has **no BYOT team authority** until the customer-signed team certificate and address facts match. Do not infer team authority from identity custody.
|
|
39
39
|
|
|
40
|
+
For E2E messaging, custody and team membership are still not enough by themselves: the recipient's encryption public key must be identity-authorized as described in `docs/e2e-messaging-contract.md`. Team/namespace authority may distribute that assertion, but it must not replace the member's key. If an encryption-key check fails, do not suggest a team-controller workaround or plaintext fallback; stop and route the user to the approved identity/key setup or recovery flow.
|
|
41
|
+
|
|
40
42
|
## Readiness checks (membership level)
|
|
41
43
|
|
|
42
44
|
Start with:
|