@awebai/oats 0.22.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 (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +380 -0
  3. package/bin/oats.mjs +3294 -0
  4. package/capabilities/oats-authoring/oats.json +12 -0
  5. package/capabilities/oats-aweb/bin/oats-aweb.mjs +297 -0
  6. package/capabilities/oats-aweb/injects/aweb.md +55 -0
  7. package/capabilities/oats-aweb/oats.json +47 -0
  8. package/capabilities/oats-aweb/package.json +20 -0
  9. package/capabilities/oats-jira/bin/oats-jira.mjs +40 -0
  10. package/capabilities/oats-jira/injects/jira.md +10 -0
  11. package/capabilities/oats-jira/oats.json +22 -0
  12. package/capabilities/oats-jira/package.json +25 -0
  13. package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +176 -0
  14. package/capabilities/oats-linear/README.md +234 -0
  15. package/capabilities/oats-linear/bin/oats-linear-hook.mjs +32 -0
  16. package/capabilities/oats-linear/bin/oats-linear.mjs +344 -0
  17. package/capabilities/oats-linear/injects/linear.md +8 -0
  18. package/capabilities/oats-linear/oats.json +24 -0
  19. package/capabilities/oats-linear/package.json +29 -0
  20. package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +217 -0
  21. package/capabilities/oats-linear/test/oats-linear.test.mjs +168 -0
  22. package/capabilities/oats-okf/agents/memory-harvest.md +25 -0
  23. package/capabilities/oats-okf/bin/oats-okf.mjs +283 -0
  24. package/capabilities/oats-okf/injects/okf.md +62 -0
  25. package/capabilities/oats-okf/oats.json +20 -0
  26. package/capabilities/oats-okf/package.json +22 -0
  27. package/capabilities/oats-okf/skills/memory-harvest/SKILL.md +100 -0
  28. package/capabilities/oats-okf/skills/okf/SKILL.md +107 -0
  29. package/capabilities/oats-okf/skills/okf/scripts/okf-validate.mjs +123 -0
  30. package/capabilities/oats-review/agents/reviewer/AGENTS.md +53 -0
  31. package/capabilities/oats-review/agents/reviewer/soul.yaml +6 -0
  32. package/capabilities/oats-review/injects/review.md +68 -0
  33. package/capabilities/oats-review/oats.json +10 -0
  34. package/capabilities/oats-review/skills/code-review/SKILL.md +44 -0
  35. package/capabilities/oats-review/skills/security-review/SKILL.md +59 -0
  36. package/docs/capabilities.md +505 -0
  37. package/docs/capability-manifest.schema.json +223 -0
  38. package/docs/configuration.md +482 -0
  39. package/docs/conventions.md +73 -0
  40. package/docs/design/desktop-ux-plan.md +362 -0
  41. package/docs/design/package-engine-contract.md +813 -0
  42. package/docs/design/package-runtime-api.md +414 -0
  43. package/docs/desktop-cli-api.md +89 -0
  44. package/docs/desktop-succession.md +51 -0
  45. package/docs/desktop.md +187 -0
  46. package/docs/implementation.md +282 -0
  47. package/docs/integrations.md +123 -0
  48. package/docs/knowledge-theory.md +97 -0
  49. package/docs/knowledge.md +139 -0
  50. package/docs/layers.md +110 -0
  51. package/docs/migration-from-oas.md +122 -0
  52. package/docs/oats-config.schema.json +140 -0
  53. package/docs/oats-lock.schema.json +175 -0
  54. package/docs/oats-package.schema.json +129 -0
  55. package/docs/packages.md +442 -0
  56. package/docs/release-lane.md +132 -0
  57. package/docs/release-notes/v0.18.2.md +79 -0
  58. package/docs/release-notes/v0.18.3.md +63 -0
  59. package/docs/release-notes/v0.18.4.md +65 -0
  60. package/docs/release-notes/v0.18.5.md +66 -0
  61. package/docs/release-notes/v0.18.6.md +87 -0
  62. package/docs/release-notes/v0.19.0.md +186 -0
  63. package/docs/release-notes/v0.19.1.md +24 -0
  64. package/docs/release-notes/v0.19.2.md +32 -0
  65. package/docs/release-notes/v0.19.3.md +16 -0
  66. package/docs/release-notes/v0.19.4.md +12 -0
  67. package/docs/release-notes/v0.20.0.md +89 -0
  68. package/docs/release-notes/v0.22.0.md +180 -0
  69. package/docs/souls-and-instances.md +344 -0
  70. package/injects/framework-workspace.md +7 -0
  71. package/injects/instance-boundary.md +43 -0
  72. package/injects/local-soul.md +19 -0
  73. package/injects/oats.md +11 -0
  74. package/injects/work-attached.md +16 -0
  75. package/injects/work-checkout.md +12 -0
  76. package/injects/work-workspace.md +22 -0
  77. package/injects/work-worktree.md +13 -0
  78. package/lib/core.mjs +6792 -0
  79. package/lib/packages.mjs +1375 -0
  80. package/lib/tmux-config.mjs +35 -0
  81. package/package-catalog.json +44 -0
  82. package/package.json +55 -0
  83. package/packages/record/README.md +151 -0
  84. package/packages/record/bin/capture.mjs +256 -0
  85. package/packages/record/bin/recall.mjs +102 -0
  86. package/packages/record/bin/setup.mjs +281 -0
  87. package/packages/record/bin/turn-record.mjs +47 -0
  88. package/packages/record/docs/turn-record-sot.md +350 -0
  89. package/packages/record/lib/canonical.mjs +151 -0
  90. package/packages/record/lib/capture-aw.mjs +179 -0
  91. package/packages/record/lib/capture-cc.mjs +322 -0
  92. package/packages/record/lib/formats.mjs +296 -0
  93. package/packages/record/lib/ignore.mjs +155 -0
  94. package/packages/record/lib/index-db.mjs +524 -0
  95. package/packages/record/lib/project-aweb.mjs +251 -0
  96. package/packages/record/lib/segments.mjs +186 -0
  97. package/packages/record/lib/store.mjs +447 -0
  98. package/packages/record/lib/tags.mjs +114 -0
  99. package/packages/record/package.json +45 -0
  100. package/packages/record/test/vectors/README.md +34 -0
  101. package/packages/record/test/vectors/aweb-projection-v1.json +231 -0
  102. package/packages/record/test/vectors/journal-merge-v1.json +261 -0
  103. package/packages/record/test/vectors/turn-id-v1.json +152 -0
  104. package/packages/record/test/vectors/validate.mjs +391 -0
  105. package/skills/integration-authoring/SKILL.md +76 -0
  106. package/skills/oats/SKILL.md +151 -0
  107. package/skills/oats-config/SKILL.md +162 -0
  108. package/skills/oats-getting-started/SKILL.md +159 -0
  109. package/skills/oats-packages/SKILL.md +180 -0
  110. package/skills/oats-support/SKILL.md +79 -0
  111. package/skills/skill-craft/SKILL.md +109 -0
  112. package/skills/soul-craft/SKILL.md +109 -0
@@ -0,0 +1,12 @@
1
+ {
2
+ "capability": "oats.authoring",
3
+ "version": "1.0.0",
4
+ "compatibility": { "oats": ">=0.6.2" },
5
+ "description": "Additive framework-authoring guidance for capability packages, agent skills, and souls.",
6
+ "requires": [],
7
+ "skills": [
8
+ "../../skills/integration-authoring",
9
+ "../../skills/skill-craft",
10
+ "../../skills/soul-craft"
11
+ ]
12
+ }
@@ -0,0 +1,297 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * oats-aweb — OATS messaging-provider hooks for aweb.
4
+ *
5
+ * Invoked by the OATS kernel at instance lifecycle events (hook contract):
6
+ * oats-aweb spawn mint a team-scoped aweb identity for the instance
7
+ * oats-aweb retire gracefully self-delete it (BEFORE the home dir is removed)
8
+ * oats-aweb roster list the aweb team's members — the cross-machine directory
9
+ * of live instances (alias = instance name) and humans
10
+ * oats-aweb setup guided onboarding: check the aw CLI, initialize the team
11
+ * scope's aweb workspace, create/join the team
12
+ *
13
+ * Env contract (set by the kernel):
14
+ * OATS_EVENT spawn|retire
15
+ * OATS_INSTANCE instance name (used as the aweb alias)
16
+ * OATS_HOME instance home dir (cwd is also set to it)
17
+ * OATS_CONTEXT resolution context dir (the soul's repo / agents root parent)
18
+ * OATS_WORKSPACE the agents root's parent — the team boundary
19
+ * OATS_SETTINGS JSON of the provider's `settings:` block
20
+ * OATS_TEAM_NAME/OATS_TEAM_ID/OATS_TEAM_SCOPE resolved config `team:` block (may be empty)
21
+ * OATS_META JSON persisted from this hook's previous spawn output (retire only)
22
+ *
23
+ * Output (spawn, stdout JSON):
24
+ * { "meta": {...persisted to instance.json + OATS_META at retire},
25
+ * "brief": "one-line TASK.md briefing line", "warning": "non-fatal problem" }
26
+ *
27
+ * EXIT CODE IS THE CONTRACT for the spawn hook. This capability declares
28
+ * `spawn` as required (oats.json), so a nonzero exit fails the spawn and rolls it
29
+ * back. Messaging is the whole point of the capability: an instance whose
30
+ * identity was never minted believes it can be woken by mail and cannot, which
31
+ * is worse than not starting. So identity failures exit nonzero, while genuinely
32
+ * advisory problems (the Claude channel plugin, a team-name mismatch) stay
33
+ * warnings on exit 0.
34
+ *
35
+ * On a fatal path, ALWAYS emit any metadata gathered so far before exiting: the
36
+ * kernel feeds it to the retire hook to compensate partial state, and an
37
+ * identity joined moments before the failure must still be deletable.
38
+ */
39
+ import { execFileSync } from "node:child_process";
40
+ import { existsSync, statSync } from "node:fs";
41
+ import { join, dirname, resolve, delimiter } from "node:path";
42
+
43
+ /** Run a command as ARGV — never a shell string. Team ids, aliases, instance
44
+ * names and invite tokens all flow through here; quoting them correctly is a
45
+ * property of one helper staying correct forever, while argv removes the class.
46
+ * This hook is a REQUIRED spawn hook, so it gates every spawn, which is reason
47
+ * enough not to rely on quoting. */
48
+ const run = (argv, cwd, timeout = 45000, { secrets = [], secretSafe = false } = {}) => {
49
+ try {
50
+ return execFileSync(argv[0], argv.slice(1), { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout }).trim();
51
+ } catch (e) {
52
+ // execFileSync puts the WHOLE ARGV in e.message ("Command failed: aw team
53
+ // join <token> …"). This hook's failures are reported by the kernel and land
54
+ // in CLI/Desktop logs, so a failed join disclosed a still-valid team invite
55
+ // token to anyone reading them (reviewer-aggregate2). Rebuild the error from
56
+ // the command name and the status — never the argv.
57
+ //
58
+ // secretSafe drops the child's OUTPUT as well. Scrubbing known strings is not
59
+ // enough for a command that MINTS a secret: `aw team invite` can print a
60
+ // freshly-created token to stderr while failing, and at that point the caller
61
+ // has no value to scrub because the token is exactly what it never received
62
+ // (reviewer-1a6e82e). For those commands, status plus fixed context is all
63
+ // the diagnosis anyone gets.
64
+ const scrub = (t) => secrets.filter(Boolean).reduce((acc, sec) => acc.split(sec).join("<redacted>"), String(t ?? ""));
65
+ const where = [argv[0], argv[1], argv[2]].filter((a) => a && !secrets.includes(a) && !a.startsWith("-")).join(" ");
66
+ const why = secretSafe ? "" : (scrub(e.stderr).trim() || (e.status === undefined ? String(e.code || "failed") : ""));
67
+ const err = new Error(`${where} failed${e.status === undefined ? "" : ` (exit ${e.status})`}${why ? `: ${why}` : ""}${secretSafe ? " (output withheld: this command handles credentials)" : ""}`);
68
+ err.status = e.status;
69
+ throw err;
70
+ }
71
+ };
72
+ /** JSON.parse whose failure never quotes the input. Node includes an excerpt of
73
+ * the malformed text in a SyntaxError, which for these commands IS the credential
74
+ * (reviewer-1a6e82e). */
75
+ const parseSecretJson = (text, what) => {
76
+ try { return JSON.parse(text); }
77
+ catch { throw new Error(`${what} returned output that is not valid JSON (withheld: this command handles credentials)`); }
78
+ };
79
+ /** Is a command on PATH? Resolved in-process rather than by running
80
+ * `command -v`, which is a SHELL BUILTIN — spawning it as a program depends on
81
+ * a /usr/bin/command binary that many systems do not ship, and its absence
82
+ * would read as "aw is missing" on every such host. */
83
+ function onPath(cmd) {
84
+ for (const dir of String(process.env.PATH || "").split(delimiter)) {
85
+ if (!dir) continue;
86
+ try { const st = statSync(join(dir, cmd)); if (st.isFile() && (st.mode & 0o111)) return true; } catch { /* keep looking */ }
87
+ }
88
+ return false;
89
+ }
90
+ const out = (o, code = 0) => { process.stdout.write(JSON.stringify(o) + "\n"); process.exit(code); };
91
+ const warn = (m) => out({ warning: `oats-aweb: ${String(m).slice(0, 300)}` });
92
+ /** Fatal for a REQUIRED spawn hook: emit metadata for compensation, then exit
93
+ * nonzero so the kernel rolls the spawn back. `meta` carries whatever external
94
+ * state already exists (e.g. a joined identity) so retire can undo it. */
95
+ const fatal = (m, meta) => out({ ...(meta ? { meta } : {}), warning: `oats-aweb: ${String(m).slice(0, 300)}` }, 1);
96
+
97
+ const event = process.env.OATS_EVENT || process.argv[2];
98
+ const instance = process.env.OATS_INSTANCE;
99
+ const home = process.env.OATS_HOME || process.cwd();
100
+
101
+ /**
102
+ * The aweb root (minting authority). BOUNDED candidates — the deployment's team
103
+ * scope (from config `team:`) is the natural home; we never walk past the
104
+ * workspace to the laptop root (a `.aw` there would be a different team;
105
+ * minting into it would be a silent cross-team leak):
106
+ * 1. the declared team scope (OATS_TEAM_SCOPE)
107
+ * 2. the instance home itself
108
+ * 3. the git repo root containing the home (if any)
109
+ * 4. the resolution context (the soul's target repo) and its git repo root
110
+ * 5. the workspace root (OATS_WORKSPACE — e.g. ~/lfx)
111
+ * First candidate with a `.aw` wins; none → no minting.
112
+ */
113
+ function gitRootOf(startDir) {
114
+ let d = resolve(startDir);
115
+ while (true) {
116
+ if (existsSync(join(d, ".git"))) return d;
117
+ const parent = dirname(d);
118
+ if (parent === d) return undefined;
119
+ d = parent;
120
+ }
121
+ }
122
+ function awebRoot() {
123
+ const candidates = [];
124
+ const push = (p) => { if (p && !candidates.includes(resolve(p))) candidates.push(resolve(p)); };
125
+ push(process.env.OATS_TEAM_SCOPE);
126
+ push(home);
127
+ push(gitRootOf(home));
128
+ push(process.env.OATS_CONTEXT);
129
+ if (process.env.OATS_CONTEXT) push(gitRootOf(process.env.OATS_CONTEXT));
130
+ push(process.env.OATS_WORKSPACE);
131
+ for (const c of candidates) if (existsSync(join(c, ".aw"))) return c;
132
+ return undefined;
133
+ }
134
+
135
+ /** Team memberships from `aw team list --json`. The current CLI returns
136
+ * `memberships`; older output used `teams`. Spawn resolution and `oats aweb
137
+ * setup` MUST read this the same way — they drifted once, and because spawn
138
+ * treats "no matching membership" as fatal, the stale reader turned every
139
+ * name-only team config into a blocked spawn (reviewer-602627c). */
140
+ const teamMemberships = (listed) => listed?.memberships || listed?.teams || [];
141
+ const teamIdsOf = (listed) => teamMemberships(listed).map((m) => m.team_id || m.id || m);
142
+
143
+ const AW_INSTALL = "install the aw CLI first — see https://aweb.ai/docs (or `oats aweb setup` for guided onboarding)";
144
+ const isCommand = ["roster", "setup"].includes(event);
145
+ if (!onPath("aw")) {
146
+ if (isCommand) { console.error(`oats aweb ${event}: aw CLI not on PATH — ${AW_INSTALL}`); process.exit(1); }
147
+ if (event === "spawn") fatal(`aw CLI not on PATH, so no identity could be minted and this instance would have no messaging — ${AW_INSTALL}`);
148
+ warn(`aw CLI not on PATH — no identity minted; ${AW_INSTALL}`);
149
+ }
150
+
151
+ if (event === "spawn") {
152
+ let minted; // external identity, once `aw team join` succeeds
153
+ const root = awebRoot();
154
+ if (!root) fatal(`no initialized aweb root (.aw) among the bounded candidates (home, its git repo, context repo, workspace ${process.env.OATS_WORKSPACE || "?"}), so no identity could be minted and this instance would have no messaging — run \`oats aweb setup\` for guided onboarding`);
155
+ try {
156
+ // Team correctness: the config's `team:` block wins (id, then name), else the
157
+ // root's active team. ALWAYS pass --team-id explicitly — never inherit whatever
158
+ // team happens to be active at mint time — and verify the joined cert matches.
159
+ // The instance name IS the discoverable alias (the team roster doubles as the
160
+ // cross-machine instance directory).
161
+ let team = process.env.OATS_TEAM_ID || process.env.OATS_TEAM_NAME;
162
+ if (!team) team = JSON.parse(run(["aw", "team", "list", "--json"], root)).active_team;
163
+ if (!team) fatal("cannot determine target team (no config team block, no active team at root), so no identity could be minted — set a team: block in oats-config.yaml, or activate a team at the aweb root");
164
+ // A bare team name (no namespace) resolves against the root's memberships.
165
+ if (!team.includes(":")) {
166
+ const teams = JSON.parse(run(["aw", "team", "list", "--json"], root));
167
+ const match = teamIdsOf(teams).filter((tid) => String(tid).startsWith(`${team}:`));
168
+ if (match.length === 1) team = match[0];
169
+ else if (match.length > 1) fatal(`team name "${team}" is ambiguous at ${root}: ${match.join(", ")}, so no identity could be minted — set team.id in oats-config.yaml`);
170
+ else fatal(`no membership matching team "${team}" at ${root}, so no identity could be minted — join or create it first (aweb-team-membership skill), or set team.id`);
171
+ }
172
+ // Both of these carry the invite token — one mints it, the other spends it —
173
+ // so neither their output nor their diagnostics may reach a log.
174
+ const inv = parseSecretJson(run(["aw", "team", "invite", "--team-id", team, "--json"], root, 45000, { secretSafe: true }), "aw team invite");
175
+ if (!inv?.token || typeof inv.token !== "string") fatal("aw team invite returned no usable token, so no identity could be minted");
176
+ const raw = parseSecretJson(run(["aw", "team", "join", inv.token, "--name", instance, "--json"], home, 45000, { secrets: [inv.token], secretSafe: true }), "aw team join");
177
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) fatal("aw team join returned no usable result, so no identity could be minted", minted);
178
+ // The RESPONSE is not a safe place to take strings from. Suppressing the
179
+ // failure paths does nothing if a successful reply is copied into meta and
180
+ // the briefing verbatim: a response echoing the invite token back as the
181
+ // alias would print it twice, on exit 0 (reviewer-a6aa1c5). Accept a field
182
+ // only if it is a plausible value of its own kind and is not carrying the
183
+ // token; otherwise fall back to what WE asked for, which is always known.
184
+ const clean = (v) => (typeof v === "string" && v.trim() && !v.includes(inv.token) ? v.trim() : undefined);
185
+ const joined = {
186
+ alias: (() => { const a = clean(raw.alias); return a && /^[a-z0-9][a-z0-9._-]{0,127}$/i.test(a) ? a : instance; })(),
187
+ // Team ids are "<name>:<domain>"; anything else is not one, and the
188
+ // requested team is the honest fallback.
189
+ team_id: (() => { const t = clean(raw.team_id); return t && /^[^\s:]+:[^\s:]+$/.test(t) ? t : team; })(),
190
+ };
191
+ // External state now exists. Record it immediately so any later failure can
192
+ // still report it for compensation.
193
+ minted = { team: joined.team_id, alias: joined.alias };
194
+ run(["aw", "init", "--do-not-touch-agents-md"], home);
195
+ const alias = joined.alias;
196
+ const mismatch = joined.team_id !== team
197
+ ? ` [WARNING: joined ${joined.team_id}, expected ${team}]` : "";
198
+ // Runtime integration: for Claude Code sessions the aweb-channel plugin
199
+ // carries real-time push events. This hook does NOT install it. The plugin
200
+ // is a DECLARED runtime requirement (oats.json), consented once at
201
+ // `oats install` and verified by the kernel before spawn — installing it here
202
+ // would mutate the operator's Claude configuration without asking, inside a
203
+ // spawn, which is exactly the silent host mutation the consent gate exists
204
+ // to prevent. By the time this runs the kernel has already proven the plugin
205
+ // is present and enabled, so contributing the flag is safe.
206
+ const launch = (process.env.OATS_RUNTIME || "") === "claude"
207
+ ? { claude: "--dangerously-load-development-channels plugin:aweb-channel@awebai-marketplace" }
208
+ : undefined;
209
+ const channelWarning = undefined;
210
+ out({
211
+ meta: { team: joined.team_id, alias },
212
+ brief: `Comms: you have an aweb identity — alias "${alias}" on team ${joined.team_id}.${mismatch} Use \`aw mail\`/\`aw chat\` for messaging (see the aweb-messaging skill); coordination stays in your deployment's task layer.`,
213
+ ...(launch ? { launch } : {}),
214
+ ...(mismatch ? { warning: `oats-aweb: team mismatch — joined ${joined.team_id}, expected ${team}` } : channelWarning ? { warning: channelWarning } : {}),
215
+ });
216
+ } catch (e) {
217
+ // A join may already have created a REMOTE identity before the failure.
218
+ // Hand it back as meta so the kernel's compensation can delete it — losing
219
+ // it here would strand a roster entry no one owns.
220
+ fatal(`identity minting failed: ${e.message || e}`, minted);
221
+ }
222
+ } else if (event === "retire") {
223
+ const meta = JSON.parse(process.env.OATS_META || "{}");
224
+ // No alias means the spawn hook never reported an identity: nothing exists to
225
+ // undo, which is completion. An alias WITH no local `.aw` is the opposite —
226
+ // the remote record exists and its key is gone, so the self-delete cannot be
227
+ // authenticated and the cleanup is incomplete, not vacuous (reviewer-602627c).
228
+ if (!meta.alias) out({ meta: { retired: false, reason: "nothing-to-delete" } });
229
+ if (!existsSync(join(home, ".aw"))) {
230
+ out({ meta: { retired: false, reason: "no-local-identity-key" }, warning: `oats-aweb: alias "${meta.alias}" was minted but ${join(home, ".aw")} is gone, so the remote record cannot be self-deleted and will linger until stale` }, 1);
231
+ }
232
+ try {
233
+ // Self-delete from inside the home, authenticated by its own key — a remote
234
+ // delete would 409 until the server marks the workspace stale.
235
+ run(["aw", "workspace", "delete", meta.alias], home);
236
+ out({ meta: { retired: true } });
237
+ } catch (e) {
238
+ // Exit nonzero: during a required-hook rollback this is the signal that
239
+ // compensation did NOT complete, so the spawn is not reported as cleanly
240
+ // rolled back while a remote identity still exists.
241
+ out({ meta: { retired: false, reason: "self-delete-failed" }, warning: `oats-aweb: self-delete failed (the remote record will linger until stale): ${e.message || e}` }, 1);
242
+ }
243
+ } else if (event === "roster") {
244
+ // Cross-machine directory: every OATS-spawned instance joins the team with
245
+ // alias = instance name, so the team's member roster lists live instances
246
+ // wherever they run (plus human members). Local liveness comes from
247
+ // `oats status --team`; this is the network view.
248
+ const root = awebRoot();
249
+ if (!root) { console.error("oats aweb roster: no initialized aweb root (.aw) found"); process.exit(1); }
250
+ const team = process.env.OATS_TEAM_ID || process.env.OATS_TEAM_NAME || JSON.parse(run(["aw", "team", "list", "--json"], root)).active_team;
251
+ if (!team) { console.error("oats aweb roster: cannot determine team (no config team block, no active team)"); process.exit(1); }
252
+ const teamFlag = team.includes(":") ? ["--team-id", team] : ["--team", team];
253
+ const r = JSON.parse(run(["aw", "id", "team", "members", ...teamFlag, "--json"], root, 60000));
254
+ if (process.argv.includes("--json")) { console.log(JSON.stringify(r, null, 2)); process.exit(0); }
255
+ console.log(`aweb team ${r.team_id || team} — member roster (cross-machine):`);
256
+ const members = r.members || [];
257
+ if (!members.length) console.log(" (no member certificates visible from this workspace)");
258
+ for (const m of members) console.log(` ${m.alias || m.name || m.did || JSON.stringify(m)}`);
259
+ console.log("\nAliases minted by OATS are instance names; message one with `aw mail send --to <alias> --subject \"...\" --body \"...\"`.");
260
+ process.exit(0);
261
+ } else if (event === "setup") {
262
+ // Guided onboarding — idempotent, prints what it finds and the one next step.
263
+ const scope = process.env.OATS_TEAM_SCOPE || process.cwd();
264
+ const teamName = process.env.OATS_TEAM_NAME;
265
+ const teamId = process.env.OATS_TEAM_ID;
266
+ console.log(`aweb onboarding — team scope: ${scope}${teamName ? `, config team: ${teamName}${teamId ? ` (${teamId})` : ""}` : ""}\n`);
267
+ if (!teamName) {
268
+ console.log("1. Declare your team in the deployment scope's oats-config.yaml first:");
269
+ console.log(" team:\n name: <your-team>\n then re-run `oats aweb setup` from there.");
270
+ process.exit(0);
271
+ }
272
+ if (!existsSync(join(scope, ".aw"))) {
273
+ console.log(`No aweb workspace at the team scope yet. Initialize it (interactive — creates or connects an aweb account):`);
274
+ console.log(` cd ${scope} && aw init`);
275
+ console.log(" First time on aweb? `aw init` walks you through creating a hosted aweb.ai account.");
276
+ console.log(" Own your domain? Use `aw init --byod` (see the aweb-team-membership skill).");
277
+ process.exit(0);
278
+ }
279
+ let teams = { memberships: [] };
280
+ try { teams = JSON.parse(run(["aw", "team", "list", "--json"], scope)); } catch { /* fall through */ }
281
+ const want = teamId || teamName;
282
+ const match = teamIdsOf(teams).find((tid) => String(tid) === want || String(tid).startsWith(`${want}:`));
283
+ if (match) {
284
+ console.log(`✓ aweb workspace initialized and member of ${match}.`);
285
+ if (teams.active_team && teams.active_team !== match) console.log(` Note: active team is ${teams.active_team}; instances join ${match} explicitly, but consider \`aw team switch ${match}\`.`);
286
+ console.log(" Done — spawned instances will join this team automatically (alias = instance name).");
287
+ console.log(" Roster: `oats aweb roster` · local: `oats status --team`");
288
+ } else {
289
+ console.log(`Workspace initialized, but no membership matching "${want}".`);
290
+ console.log(` Create the team: cd ${scope} && aw team create ${teamName}`);
291
+ console.log(" Or join an existing one: get an invite token from a member, then `aw team join <token>`");
292
+ console.log(" (details: aweb-team-membership skill)");
293
+ }
294
+ process.exit(0);
295
+ } else {
296
+ warn(`unknown event "${event}" (expected spawn|retire)`);
297
+ }
@@ -0,0 +1,55 @@
1
+ ## Messaging: aweb
2
+
3
+ Your messaging layer is **aweb**. You have (or will be minted) a team-scoped
4
+ aweb identity — alias = your instance name — on your deployment's team (see
5
+ `instance.json` / your TASK.md briefing for the team).
6
+
7
+ **Load the skills at the right moments — do not work from memory:**
8
+
9
+ - **Before your first `aw mail`/`aw chat` of a session**, load the
10
+ **aweb-messaging** skill. It is the
11
+ playbook for sending, replying, and chat etiquette.
12
+ - **When an aweb channel event awakens you**, read the injected event
13
+ metadata first, then load the **aweb-messaging** skill ("Read the event
14
+ first" section) before responding — continue the existing conversation,
15
+ never
16
+ start a new thread when a `message_id`/`conversation_id` is provided.
17
+ - For team/roster/certificate questions, load **aweb-team-membership**; for
18
+ identity/key questions, load **aweb-identity**.
19
+ - If a command errors or a flag looks wrong, re-read the skill or run
20
+ `aw <cmd> --help` — never invent flags.
21
+
22
+ Quick crib (the skill has the full craft; run from your instance home):
23
+
24
+ ```bash
25
+ aw mail inbox # UNREAD mail only
26
+ aw mail inbox --show-all # full history (read mail is not lost)
27
+ aw mail send --to <alias> --subject "..." --body "..." # recipient needs --to
28
+ aw mail send --to <alias> --subject "..." --body-file <f> # markdown/backticks
29
+ aw mail reply <message-id> --body "..." # reply on an existing thread
30
+ aw chat send --to <alias> --body "..." # synchronous back-and-forth
31
+ ```
32
+
33
+ Flags are exactly these — there is no positional recipient and no
34
+ `--reply-to`; when unsure run `aw mail send --help`, don't guess. For any
35
+ body longer than a sentence (or containing quotes/backticks/newlines),
36
+ write it to a temp file and use `--body-file` — inline `--body` shell
37
+ escaping is a recurring failure.
38
+
39
+ Aliases are instance names (e.g. `dev-coordinator-1`). Discovery:
40
+ `oats status --team` lists this machine's live instances; `oats aweb roster`
41
+ lists the aweb team across machines.
42
+
43
+ **Never sleep, poll, or busy-wait for another agent's reply.** Send your
44
+ message, finish your turn, and go idle — the aweb channel awakens your
45
+ session the moment mail or chat arrives (you saw `✓ aweb connected` at
46
+ startup). A `sleep N; aw mail inbox` loop burns tokens, delays the reply,
47
+ and adds nothing. An empty `aw mail inbox` means no UNREAD mail — not that
48
+ messages were lost.
49
+
50
+ If messaging fails or your identity is missing, `oats aweb setup` diagnoses
51
+ the deployment's aweb state and prints the next step (report it to your
52
+ human rather than re-onboarding yourself).
53
+
54
+ Messaging only: task coordination lives in your deployment's task layer, and
55
+ `aw task`/`work`/`lock`/`roles` are not part of this integration.
@@ -0,0 +1,47 @@
1
+ {
2
+ "capability": "oats.aweb",
3
+ "command": "aweb",
4
+ "version": "1.8.0",
5
+ "compatibility": {
6
+ "oats": ">=0.6.2"
7
+ },
8
+ "layer": "messaging",
9
+ "description": "Messaging layer via aweb: per-instance team identities + native aw mail/chat skills + cross-machine team roster.",
10
+ "requires": [
11
+ {
12
+ "command": "aw",
13
+ "why": "identity minting at spawn, self-delete at retire, and all agent messaging",
14
+ "install": "https://aweb.ai/docs (aw CLI)"
15
+ },
16
+ {
17
+ "runtime": "pi",
18
+ "package": "npm:@awebai/pi",
19
+ "why": "the aweb channel extension for pi sessions \u2014 real-time mail/chat awakenings; without it a pi instance can send with `aw` but is never woken by incoming messages",
20
+ "install": "https://aweb.ai/docs (installed into pi with `pi install npm:@awebai/pi`)"
21
+ },
22
+ {
23
+ "runtime": "claude",
24
+ "package": "aweb-channel@awebai-marketplace",
25
+ "marketplace": "awebai/claude-plugins",
26
+ "why": "the aweb channel plugin for Claude Code sessions \u2014 real-time mail/chat awakenings; without it a Claude instance can send with `aw` but is never woken by incoming messages",
27
+ "install": "https://aweb.ai/docs (installs the awebai marketplace and the aweb-channel plugin into Claude Code)"
28
+ }
29
+ ],
30
+ "skills": [
31
+ "node_modules/@awebai/pi/skills/aweb-messaging",
32
+ "node_modules/@awebai/pi/skills/aweb-team-membership",
33
+ "node_modules/@awebai/pi/skills/aweb-identity"
34
+ ],
35
+ "inject": "injects/aweb.md",
36
+ "commands": {
37
+ "roster": "bin/oats-aweb.mjs roster",
38
+ "setup": "bin/oats-aweb.mjs setup"
39
+ },
40
+ "hooks": {
41
+ "spawn": {
42
+ "command": "bin/oats-aweb.mjs spawn",
43
+ "required": true
44
+ },
45
+ "retire": "bin/oats-aweb.mjs retire"
46
+ }
47
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@awebai/oats-aweb",
3
+ "version": "0.2.0",
4
+ "description": "OATS messaging provider: per-instance aweb identities (mint at spawn, self-delete at retire), cross-machine roster, guided setup, and agents-md injection",
5
+ "keywords": [
6
+ "pi-package",
7
+ "oats",
8
+ "aweb"
9
+ ],
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/awebai/oats",
13
+ "directory": "capabilities/oats-aweb"
14
+ },
15
+ "license": "MIT",
16
+ "type": "module",
17
+ "dependencies": {
18
+ "@awebai/pi": "^0.2.1"
19
+ }
20
+ }
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * oats-jira — OATS tasks-provider hook for Jira.
4
+ *
5
+ * Invoked by the OATS kernel at instance lifecycle events (hook contract):
6
+ * oats-jira spawn surface the instance's Jira identity (label) and the
7
+ * deployment's site/project in TASK.md — advisory only,
8
+ * no Jira calls, nothing to mint or clean up.
9
+ *
10
+ * Env contract (set by the kernel):
11
+ * OATS_EVENT spawn
12
+ * OATS_INSTANCE instance name (its Jira label is agent-<instance>)
13
+ * OATS_SETTINGS JSON of the provider's `settings:` block ({ site?, project? })
14
+ *
15
+ * Output (stdout JSON): { "meta": {...}, "brief": "...", "warning": "..." }
16
+ * Exit code is advisory: the kernel treats hook failure as a warning, never a block.
17
+ */
18
+ const out = (o) => { process.stdout.write(JSON.stringify(o) + "\n"); process.exit(0); };
19
+ const warn = (m) => out({ warning: `oats-jira: ${String(m).slice(0, 300)}` });
20
+
21
+ const event = process.env.OATS_EVENT || process.argv[2];
22
+ const instance = process.env.OATS_INSTANCE;
23
+ const settings = JSON.parse(process.env.OATS_SETTINGS || "{}");
24
+
25
+ if (event === "spawn") {
26
+ const label = `agent-${instance}`;
27
+ const site = settings.site;
28
+ const project = settings.project;
29
+ const where = site && project ? `project ${project} on ${site}`
30
+ : site ? `site ${site} (project unset — ask your human or check oats doctor)`
31
+ : project ? `project ${project} (site unset — ask your human or check oats doctor)`
32
+ : `your deployment's Jira (site/project not configured — ask your human, or set capabilities.oats.jira.<target>.settings in oats-config.yaml)`;
33
+ out({
34
+ meta: { label, ...(site ? { site } : {}), ...(project ? { project } : {}) },
35
+ brief: `Tasks: Jira — ${where}. Your Jira identity is the label "${label}" (never the assignee field). Load the jira-tasks skill before touching tickets.`,
36
+ ...(site && project ? {} : { warning: `oats-jira: settings incomplete (site: ${site || "unset"}, project: ${project || "unset"}) — set capabilities.oats.jira.<target>.settings.{site,project} in oats-config.yaml` }),
37
+ });
38
+ } else {
39
+ warn(`unknown event "${event}" (expected spawn)`);
40
+ }
@@ -0,0 +1,10 @@
1
+ ## Tasks: Jira
2
+
3
+ Your tasks layer is **Jira** (via the `acli` CLI). Work traces up to epics;
4
+ you are identified by the label `agent-<your-instance-alias>` and an `Agent:`
5
+ line in descriptions — never by the assignee field. Load the **jira-tasks**
6
+ skill before reading your work queue, joining an epic's roster, posting
7
+ progress, transitioning status, or creating stories/tasks. Your Jira site and
8
+ project come from your deployment's settings (see your TASK.md briefing or
9
+ the skill). Tasks only: status and outcomes live in Jira; conversation lives
10
+ in your deployment's messaging layer.
@@ -0,0 +1,22 @@
1
+ {
2
+ "capability": "oats.jira",
3
+ "command": "jira",
4
+ "version": "1.0.0",
5
+ "compatibility": { "oats": ">=0.6.2" },
6
+ "layer": "tasks",
7
+ "description": "Tasks layer via Jira: acli-based epic/story/task protocol, agent roster in epic descriptions, label-based agent identity.",
8
+ "requires": [
9
+ {
10
+ "command": "acli",
11
+ "why": "all Jira operations (search, view, create, transition, comment)",
12
+ "install": "https://developer.atlassian.com/cloud/acli/guides/install-acli/"
13
+ }
14
+ ],
15
+ "skills": [
16
+ "skills"
17
+ ],
18
+ "inject": "injects/jira.md",
19
+ "hooks": {
20
+ "spawn": "bin/oats-jira.mjs spawn"
21
+ }
22
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@awebai/oats-jira",
3
+ "version": "0.1.0",
4
+ "description": "OATS tasks provider: Jira via acli — epic/story/task protocol, agent roster, label-based agent identity, and agents-md injection",
5
+ "keywords": [
6
+ "pi-package",
7
+ "oats",
8
+ "jira"
9
+ ],
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/awebai/oats",
13
+ "directory": "capabilities/oats-jira"
14
+ },
15
+ "license": "MIT",
16
+ "type": "module",
17
+ "bin": {
18
+ "oats-jira": "./bin/oats-jira.mjs"
19
+ },
20
+ "pi": {
21
+ "skills": [
22
+ "./skills"
23
+ ]
24
+ }
25
+ }