mercury-agent 0.23.2 → 0.24.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 (128) hide show
  1. package/container/Dockerfile +1 -1
  2. package/container/Dockerfile.base +1 -1
  3. package/container/agent-package.json +1 -1
  4. package/docs/configuration.md +60 -0
  5. package/docs/container-lifecycle.md +27 -15
  6. package/docs/extensions.md +36 -0
  7. package/docs/goals/rehearsal-bench/roadmap.md +1 -1
  8. package/docs/goals/release-gate/roadmap.md +8 -8
  9. package/docs/goals/whatsapp-bot-hardening/open-threads.md +18 -0
  10. package/docs/goals/whatsapp-bot-hardening/roadmap.md +1 -1
  11. package/docs/graceful-shutdown.md +21 -5
  12. package/docs/pending-verification.md +195 -4
  13. package/docs/plans/{bugs-0915 → archive/2026-09-15-bugs-0915}/decisions.md +1 -0
  14. package/docs/plans/{bugs-0915 → archive/2026-09-15-bugs-0915}/plan.md +2 -2
  15. package/docs/plans/archive/2026-09-15-bugs-0915/sessions/S1.md +56 -0
  16. package/docs/plans/archive/2026-09-15-bugs-0915/sessions/S2.md +51 -0
  17. package/docs/plans/archive/2026-09-15-bugs-0915/sessions/S3.md +60 -0
  18. package/docs/plans/archive/2026-09-15-bugs-0915/sessions/S4.md +46 -0
  19. package/docs/plans/archive/2026-09-22-p0-bugs-0922/briefs/S1.md +60 -0
  20. package/docs/plans/archive/2026-09-22-p0-bugs-0922/briefs/S2.md +60 -0
  21. package/docs/plans/archive/2026-09-22-p0-bugs-0922/briefs/S3.md +58 -0
  22. package/docs/plans/archive/2026-09-22-p0-bugs-0922/briefs/S4.md +58 -0
  23. package/docs/plans/archive/2026-09-22-p0-bugs-0922/decisions.md +8 -0
  24. package/docs/plans/archive/2026-09-22-p0-bugs-0922/plan.md +102 -0
  25. package/docs/plans/archive/2026-09-22-p0-bugs-0922/sessions/S1.md +29 -0
  26. package/docs/plans/archive/2026-09-22-p0-bugs-0922/sessions/S2.md +29 -0
  27. package/docs/plans/archive/2026-09-22-p0-bugs-0922/sessions/S3.md +30 -0
  28. package/docs/plans/archive/2026-09-22-p0-bugs-0922/sessions/S4.md +32 -0
  29. package/docs/plans/bench-patterns-0915/briefs/S10.md +72 -0
  30. package/docs/plans/bench-patterns-0915/briefs/S11.md +95 -0
  31. package/docs/plans/bench-patterns-0915/briefs/S12.md +87 -0
  32. package/docs/plans/bench-patterns-0915/briefs/S9.md +81 -0
  33. package/docs/plans/bench-patterns-0915/decisions.md +7 -1
  34. package/docs/plans/bench-patterns-0915/plan.md +92 -8
  35. package/docs/plans/bench-patterns-0915/sessions/S10.md +40 -0
  36. package/docs/plans/bench-patterns-0915/sessions/S11.md +53 -0
  37. package/docs/plans/bench-patterns-0915/sessions/S12.md +44 -0
  38. package/docs/plans/bench-patterns-0915/sessions/S4.md +29 -0
  39. package/docs/plans/bench-patterns-0915/sessions/S5.md +30 -0
  40. package/docs/plans/bench-patterns-0915/sessions/S6.md +46 -0
  41. package/docs/plans/bench-patterns-0915/sessions/S7.md +30 -0
  42. package/docs/plans/bench-patterns-0915/sessions/S8.md +30 -0
  43. package/docs/plans/bench-patterns-0915/sessions/S9.md +2 -0
  44. package/docs/plans/flat-0916/briefs/S1.md +55 -0
  45. package/docs/plans/flat-0916/briefs/S2.md +55 -0
  46. package/docs/plans/flat-0916/decisions.md +8 -0
  47. package/docs/plans/flat-0916/plan.md +88 -0
  48. package/docs/plans/p0-plan-0922/briefs/S1.md +60 -0
  49. package/docs/plans/p0-plan-0922/briefs/S2.md +54 -0
  50. package/docs/plans/p0-plan-0922/briefs/S3.md +58 -0
  51. package/docs/plans/p0-plan-0922/briefs/S4.md +54 -0
  52. package/docs/plans/p0-plan-0922/briefs/S5.md +79 -0
  53. package/docs/plans/p0-plan-0922/briefs/S6.md +77 -0
  54. package/docs/plans/p0-plan-0922/briefs/S7.md +79 -0
  55. package/docs/plans/p0-plan-0922/briefs/S8.md +69 -0
  56. package/docs/plans/p0-plan-0922/decisions.md +9 -0
  57. package/docs/plans/p0-plan-0922/plan.md +215 -0
  58. package/docs/plans/p0-plan-0922/sessions/S1.md +22 -0
  59. package/docs/plans/p0-plan-0922/sessions/S2.md +22 -0
  60. package/docs/plans/p0-plan-0922/sessions/S3.md +22 -0
  61. package/docs/plans/p0-plan-0922/sessions/S4.md +22 -0
  62. package/docs/plans/p0-plan-0922/sessions/S5.md +44 -0
  63. package/docs/plans/p0-plan-0922/sessions/S6.md +60 -0
  64. package/docs/plans/p0-plan-0922/sessions/S7.md +69 -0
  65. package/docs/plans/sensitive-bugs-0922/briefs/S1.md +63 -0
  66. package/docs/plans/sensitive-bugs-0922/briefs/S2.md +62 -0
  67. package/docs/plans/sensitive-bugs-0922/briefs/S3.md +61 -0
  68. package/docs/plans/sensitive-bugs-0922/briefs/S4.md +61 -0
  69. package/docs/plans/sensitive-bugs-0922/briefs/S5.md +62 -0
  70. package/docs/plans/sensitive-bugs-0922/decisions.md +9 -0
  71. package/docs/plans/sensitive-bugs-0922/plan.md +147 -0
  72. package/docs/release-gate.md +1 -1
  73. package/docs/smoke-fixtures.md +9 -0
  74. package/examples/extensions/feed-watch/fixtures.ts +210 -0
  75. package/examples/extensions/feed-watch/index.ts +140 -9
  76. package/examples/extensions/office-365-mail/README.md +132 -0
  77. package/examples/extensions/office-365-mail/index.ts +170 -0
  78. package/examples/extensions/office-365-mail/lib/graph.ts +1031 -0
  79. package/examples/extensions/office-365-mail/scripts/login.ts +252 -0
  80. package/examples/extensions/office-365-mail/skill/SKILL.md +94 -0
  81. package/examples/profiles/football-reporter/AGENTS.md +15 -2
  82. package/examples/profiles/football-reporter/config.yaml +5 -1
  83. package/examples/profiles/tel-aviv-flat-search/AGENTS.md +5 -1
  84. package/package.json +10 -6
  85. package/resources/pi-extensions/subagent/index.ts +38 -42
  86. package/resources/pi-extensions/subagent/outcome.ts +28 -0
  87. package/src/adapters/whatsapp-thread.ts +12 -0
  88. package/src/agent/container-entry.ts +40 -7
  89. package/src/agent/container-env.ts +26 -0
  90. package/src/agent/container-error.ts +28 -0
  91. package/src/agent/container-runner.ts +50 -14
  92. package/src/audit/ledger-queries.ts +35 -3
  93. package/src/bridges/slack.ts +4 -1
  94. package/src/bridges/teams.ts +55 -15
  95. package/src/bridges/telegram.ts +3 -1
  96. package/src/cli/mercury.ts +46 -0
  97. package/src/cli/mrctl-http.ts +25 -0
  98. package/src/cli/mrctl.ts +13 -14
  99. package/src/config.ts +70 -34
  100. package/src/core/api.ts +51 -29
  101. package/src/core/boot-reconcile.ts +168 -0
  102. package/src/core/chat-run-journal.ts +383 -0
  103. package/src/core/debounce.ts +97 -1
  104. package/src/core/handler.ts +292 -44
  105. package/src/core/instance-lock.ts +159 -0
  106. package/src/core/operator-alerts.ts +80 -10
  107. package/src/core/pending-ingress.ts +298 -0
  108. package/src/core/routes/dashboard.ts +28 -6
  109. package/src/core/routes/media.ts +22 -0
  110. package/src/core/routes/shadow.ts +28 -3
  111. package/src/core/runtime.ts +139 -4
  112. package/src/core/simulate-ingress.ts +5 -2
  113. package/src/core/space-queue.ts +38 -9
  114. package/src/core/storage-cleanup.ts +16 -8
  115. package/src/core/system-messages.ts +50 -0
  116. package/src/core/task-scheduler.ts +150 -14
  117. package/src/dashboard/index.html +2 -0
  118. package/src/main.ts +121 -2
  119. package/src/ops/shadow-service.ts +113 -1
  120. package/src/ops/shadow-snapshot.ts +126 -0
  121. package/src/preflight/checks/roundtrip.ts +50 -39
  122. package/src/smoke/cli.ts +24 -4
  123. package/src/storage/db.ts +613 -89
  124. package/src/types.ts +20 -3
  125. /package/docs/plans/{bugs-0915 → archive/2026-09-15-bugs-0915}/briefs/S1.md +0 -0
  126. /package/docs/plans/{bugs-0915 → archive/2026-09-15-bugs-0915}/briefs/S2.md +0 -0
  127. /package/docs/plans/{bugs-0915 → archive/2026-09-15-bugs-0915}/briefs/S3.md +0 -0
  128. /package/docs/plans/{bugs-0915 → archive/2026-09-15-bugs-0915}/briefs/S4.md +0 -0
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Office 365 Mail — host-side capability broker extension over Microsoft Graph.
3
+ *
4
+ * Third `mercury.capability()` adopter (rung 4 of the secrets ladder, see
5
+ * docs/ideas/container-egress-unrestricted.md) after yahoo-mail and morning.
6
+ * The Entra client id, the seed refresh token and every rotated successor stay
7
+ * in the Mercury host process; the agent container invokes named verbs via
8
+ * `mrctl capability office-365-mail <action> '<json>'` and no `M365_*` value
9
+ * ever enters a container. The verbs live in `lib/graph.ts`.
10
+ */
11
+
12
+ type ConnectionCategory =
13
+ | "email"
14
+ | "drive"
15
+ | "calendar"
16
+ | "finance"
17
+ | "messaging"
18
+ | "docs"
19
+ | "workspace"
20
+ | "other";
21
+
22
+ type ConnectionAuthType =
23
+ | "oauth2"
24
+ | "apikey"
25
+ | "app-password"
26
+ | "credentials-file"
27
+ | "custom";
28
+
29
+ type ConnectionStatus = "connected" | "needs-reauth" | "broken" | "unknown";
30
+
31
+ /** Structural copy of `ConnectionHealthCheck` in src/extensions/types.ts. */
32
+ type ConnectionHealthCheck = {
33
+ recovery: string;
34
+ timeoutMs?: number;
35
+ source?: string;
36
+ run(ctx: ExtContext): Promise<CredentialProbeResult>;
37
+ };
38
+
39
+ /** The slice of `MercuryExtensionContext` this extension reads. */
40
+ type ExtContext = {
41
+ db: {
42
+ getExtState(ext: string, key: string): string | null;
43
+ setExtState(ext: string, key: string, value: string): void;
44
+ deleteExtState(ext: string, key: string): boolean;
45
+ };
46
+ };
47
+
48
+ /** Structural match for `MercuryExtensionAPI` (avoid package subpath imports). */
49
+ type MercuryExt = {
50
+ permission(opts: { defaultRoles: string[] }): void;
51
+ env(def: { from: string; as?: string; hostOnly: boolean }): void;
52
+ skill(relativePath: string): void;
53
+ connection(def: {
54
+ displayName: string;
55
+ iconUrl?: string;
56
+ category: ConnectionCategory;
57
+ authType: ConnectionAuthType;
58
+ credentialEnvVar?: string;
59
+ scopes?: string[];
60
+ statusCheck?: (
61
+ ctx: ExtContext,
62
+ ) => Promise<{ status: ConnectionStatus; detail?: string }>;
63
+ healthCheck?: ConnectionHealthCheck;
64
+ }): void;
65
+ capability(
66
+ name: string,
67
+ handler: (
68
+ req: {
69
+ name: string;
70
+ action: string;
71
+ callerId: string;
72
+ spaceId: string;
73
+ body: unknown;
74
+ },
75
+ ctx: ExtContext,
76
+ ) => Promise<{ status?: number; data: unknown }>,
77
+ ): void;
78
+ };
79
+
80
+ import {
81
+ type CredentialProbeResult,
82
+ dbTokenStore,
83
+ M365_ENV,
84
+ M365_EXT,
85
+ MailBroker,
86
+ STATE_KEYS,
87
+ } from "./lib/graph.js";
88
+
89
+ export const LOGIN_HINT =
90
+ "run `bun .mercury/extensions/office-365-mail/scripts/login.ts --client-id <id>` on the host, put the printed MERCURY_M365_* lines in .env, then restart Mercury";
91
+
92
+ /**
93
+ * One broker per host process: it owns the in-memory access token and the
94
+ * single-flight refresh, and every caller (verbs, statusCheck, the daily
95
+ * probe) must share it or the token store gets two writers.
96
+ */
97
+ let broker: MailBroker | undefined;
98
+ function brokerFor(ctx: ExtContext): MailBroker {
99
+ if (!broker) broker = new MailBroker({ store: dbTokenStore(ctx.db) });
100
+ return broker;
101
+ }
102
+
103
+ /** Test seam: drop the process-wide broker so each test starts cold. */
104
+ export function resetBrokerForTests(): void {
105
+ broker = undefined;
106
+ }
107
+
108
+ export default function (mercury: MercuryExt) {
109
+ mercury.permission({ defaultRoles: ["admin"] });
110
+
111
+ // hostOnly, and the claims must STAY: the loader rejects a
112
+ // connection.credentialEnvVar that no mercury.env() declares, and an
113
+ // UNCLAIMED MERCURY_* var falls through the blunt passthrough into every
114
+ // container under env_passthrough: "all". hostOnly is what keeps the client
115
+ // id, the seed and the tenant out of containers on both paths.
116
+ mercury.env({ from: M365_ENV.clientId, hostOnly: true });
117
+ mercury.env({ from: M365_ENV.refreshToken, hostOnly: true });
118
+ mercury.env({ from: M365_ENV.tenant, hostOnly: true });
119
+
120
+ mercury.connection({
121
+ displayName: "Office 365 Mail",
122
+ category: "email",
123
+ authType: "oauth2",
124
+ // The CLIENT ID gates loading, not the seed: the seed may be removed from
125
+ // .env once a rotated successor is in the store, and that must not unload
126
+ // a working mailbox connection.
127
+ credentialEnvVar: M365_ENV.clientId,
128
+ scopes: [
129
+ "https://graph.microsoft.com/Mail.ReadWrite",
130
+ "https://graph.microsoft.com/Mail.Send",
131
+ "https://graph.microsoft.com/User.Read",
132
+ "offline_access",
133
+ ],
134
+ // Side-effect free and fast: reports from env presence and stored state,
135
+ // never mints a token. Validity is the healthCheck's question.
136
+ statusCheck: async (ctx) => {
137
+ const authError = ctx.db.getExtState(M365_EXT, STATE_KEYS.authError);
138
+ if (authError) {
139
+ return { status: "needs-reauth", detail: authError };
140
+ }
141
+ const seed = process.env[M365_ENV.refreshToken]?.trim();
142
+ const stored = ctx.db.getExtState(M365_EXT, STATE_KEYS.refreshToken);
143
+ if (!seed && !stored) {
144
+ return {
145
+ status: "unknown",
146
+ detail: `no refresh token yet — ${LOGIN_HINT}`,
147
+ };
148
+ }
149
+ return { status: "connected" };
150
+ },
151
+ // The daily validity probe (src/core/connection-health.ts): one forced
152
+ // exchange through the broker's single-flight path, so the rotated token
153
+ // it receives is persisted by the same code the verbs use.
154
+ healthCheck: {
155
+ source: M365_ENV.refreshToken,
156
+ timeoutMs: 20_000,
157
+ recovery: LOGIN_HINT,
158
+ run: (ctx) => brokerFor(ctx).probeCredential(),
159
+ },
160
+ });
161
+
162
+ // All errors come back as data (runAction never throws) — the capability
163
+ // route would mask a thrown error behind a generic 500 and the model needs
164
+ // Graph's real message to report the cause.
165
+ mercury.capability(M365_EXT, async (req, ctx) => {
166
+ return brokerFor(ctx).runAction(req.action, req.body);
167
+ });
168
+
169
+ mercury.skill("./skill");
170
+ }