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.
- package/container/Dockerfile +1 -1
- package/container/Dockerfile.base +1 -1
- package/container/agent-package.json +1 -1
- package/docs/configuration.md +60 -0
- package/docs/container-lifecycle.md +27 -15
- package/docs/extensions.md +36 -0
- package/docs/goals/rehearsal-bench/roadmap.md +1 -1
- package/docs/goals/release-gate/roadmap.md +8 -8
- package/docs/goals/whatsapp-bot-hardening/open-threads.md +18 -0
- package/docs/goals/whatsapp-bot-hardening/roadmap.md +1 -1
- package/docs/graceful-shutdown.md +21 -5
- package/docs/pending-verification.md +195 -4
- package/docs/plans/{bugs-0915 → archive/2026-09-15-bugs-0915}/decisions.md +1 -0
- package/docs/plans/{bugs-0915 → archive/2026-09-15-bugs-0915}/plan.md +2 -2
- package/docs/plans/archive/2026-09-15-bugs-0915/sessions/S1.md +56 -0
- package/docs/plans/archive/2026-09-15-bugs-0915/sessions/S2.md +51 -0
- package/docs/plans/archive/2026-09-15-bugs-0915/sessions/S3.md +60 -0
- package/docs/plans/archive/2026-09-15-bugs-0915/sessions/S4.md +46 -0
- package/docs/plans/archive/2026-09-22-p0-bugs-0922/briefs/S1.md +60 -0
- package/docs/plans/archive/2026-09-22-p0-bugs-0922/briefs/S2.md +60 -0
- package/docs/plans/archive/2026-09-22-p0-bugs-0922/briefs/S3.md +58 -0
- package/docs/plans/archive/2026-09-22-p0-bugs-0922/briefs/S4.md +58 -0
- package/docs/plans/archive/2026-09-22-p0-bugs-0922/decisions.md +8 -0
- package/docs/plans/archive/2026-09-22-p0-bugs-0922/plan.md +102 -0
- package/docs/plans/archive/2026-09-22-p0-bugs-0922/sessions/S1.md +29 -0
- package/docs/plans/archive/2026-09-22-p0-bugs-0922/sessions/S2.md +29 -0
- package/docs/plans/archive/2026-09-22-p0-bugs-0922/sessions/S3.md +30 -0
- package/docs/plans/archive/2026-09-22-p0-bugs-0922/sessions/S4.md +32 -0
- package/docs/plans/bench-patterns-0915/briefs/S10.md +72 -0
- package/docs/plans/bench-patterns-0915/briefs/S11.md +95 -0
- package/docs/plans/bench-patterns-0915/briefs/S12.md +87 -0
- package/docs/plans/bench-patterns-0915/briefs/S9.md +81 -0
- package/docs/plans/bench-patterns-0915/decisions.md +7 -1
- package/docs/plans/bench-patterns-0915/plan.md +92 -8
- package/docs/plans/bench-patterns-0915/sessions/S10.md +40 -0
- package/docs/plans/bench-patterns-0915/sessions/S11.md +53 -0
- package/docs/plans/bench-patterns-0915/sessions/S12.md +44 -0
- package/docs/plans/bench-patterns-0915/sessions/S4.md +29 -0
- package/docs/plans/bench-patterns-0915/sessions/S5.md +30 -0
- package/docs/plans/bench-patterns-0915/sessions/S6.md +46 -0
- package/docs/plans/bench-patterns-0915/sessions/S7.md +30 -0
- package/docs/plans/bench-patterns-0915/sessions/S8.md +30 -0
- package/docs/plans/bench-patterns-0915/sessions/S9.md +2 -0
- package/docs/plans/flat-0916/briefs/S1.md +55 -0
- package/docs/plans/flat-0916/briefs/S2.md +55 -0
- package/docs/plans/flat-0916/decisions.md +8 -0
- package/docs/plans/flat-0916/plan.md +88 -0
- package/docs/plans/p0-plan-0922/briefs/S1.md +60 -0
- package/docs/plans/p0-plan-0922/briefs/S2.md +54 -0
- package/docs/plans/p0-plan-0922/briefs/S3.md +58 -0
- package/docs/plans/p0-plan-0922/briefs/S4.md +54 -0
- package/docs/plans/p0-plan-0922/briefs/S5.md +79 -0
- package/docs/plans/p0-plan-0922/briefs/S6.md +77 -0
- package/docs/plans/p0-plan-0922/briefs/S7.md +79 -0
- package/docs/plans/p0-plan-0922/briefs/S8.md +69 -0
- package/docs/plans/p0-plan-0922/decisions.md +9 -0
- package/docs/plans/p0-plan-0922/plan.md +215 -0
- package/docs/plans/p0-plan-0922/sessions/S1.md +22 -0
- package/docs/plans/p0-plan-0922/sessions/S2.md +22 -0
- package/docs/plans/p0-plan-0922/sessions/S3.md +22 -0
- package/docs/plans/p0-plan-0922/sessions/S4.md +22 -0
- package/docs/plans/p0-plan-0922/sessions/S5.md +44 -0
- package/docs/plans/p0-plan-0922/sessions/S6.md +60 -0
- package/docs/plans/p0-plan-0922/sessions/S7.md +69 -0
- package/docs/plans/sensitive-bugs-0922/briefs/S1.md +63 -0
- package/docs/plans/sensitive-bugs-0922/briefs/S2.md +62 -0
- package/docs/plans/sensitive-bugs-0922/briefs/S3.md +61 -0
- package/docs/plans/sensitive-bugs-0922/briefs/S4.md +61 -0
- package/docs/plans/sensitive-bugs-0922/briefs/S5.md +62 -0
- package/docs/plans/sensitive-bugs-0922/decisions.md +9 -0
- package/docs/plans/sensitive-bugs-0922/plan.md +147 -0
- package/docs/release-gate.md +1 -1
- package/docs/smoke-fixtures.md +9 -0
- package/examples/extensions/feed-watch/fixtures.ts +210 -0
- package/examples/extensions/feed-watch/index.ts +140 -9
- package/examples/extensions/office-365-mail/README.md +132 -0
- package/examples/extensions/office-365-mail/index.ts +170 -0
- package/examples/extensions/office-365-mail/lib/graph.ts +1031 -0
- package/examples/extensions/office-365-mail/scripts/login.ts +252 -0
- package/examples/extensions/office-365-mail/skill/SKILL.md +94 -0
- package/examples/profiles/football-reporter/AGENTS.md +15 -2
- package/examples/profiles/football-reporter/config.yaml +5 -1
- package/examples/profiles/tel-aviv-flat-search/AGENTS.md +5 -1
- package/package.json +10 -6
- package/resources/pi-extensions/subagent/index.ts +38 -42
- package/resources/pi-extensions/subagent/outcome.ts +28 -0
- package/src/adapters/whatsapp-thread.ts +12 -0
- package/src/agent/container-entry.ts +40 -7
- package/src/agent/container-env.ts +26 -0
- package/src/agent/container-error.ts +28 -0
- package/src/agent/container-runner.ts +50 -14
- package/src/audit/ledger-queries.ts +35 -3
- package/src/bridges/slack.ts +4 -1
- package/src/bridges/teams.ts +55 -15
- package/src/bridges/telegram.ts +3 -1
- package/src/cli/mercury.ts +46 -0
- package/src/cli/mrctl-http.ts +25 -0
- package/src/cli/mrctl.ts +13 -14
- package/src/config.ts +70 -34
- package/src/core/api.ts +51 -29
- package/src/core/boot-reconcile.ts +168 -0
- package/src/core/chat-run-journal.ts +383 -0
- package/src/core/debounce.ts +97 -1
- package/src/core/handler.ts +292 -44
- package/src/core/instance-lock.ts +159 -0
- package/src/core/operator-alerts.ts +80 -10
- package/src/core/pending-ingress.ts +298 -0
- package/src/core/routes/dashboard.ts +28 -6
- package/src/core/routes/media.ts +22 -0
- package/src/core/routes/shadow.ts +28 -3
- package/src/core/runtime.ts +139 -4
- package/src/core/simulate-ingress.ts +5 -2
- package/src/core/space-queue.ts +38 -9
- package/src/core/storage-cleanup.ts +16 -8
- package/src/core/system-messages.ts +50 -0
- package/src/core/task-scheduler.ts +150 -14
- package/src/dashboard/index.html +2 -0
- package/src/main.ts +121 -2
- package/src/ops/shadow-service.ts +113 -1
- package/src/ops/shadow-snapshot.ts +126 -0
- package/src/preflight/checks/roundtrip.ts +50 -39
- package/src/smoke/cli.ts +24 -4
- package/src/storage/db.ts +613 -89
- package/src/types.ts +20 -3
- /package/docs/plans/{bugs-0915 → archive/2026-09-15-bugs-0915}/briefs/S1.md +0 -0
- /package/docs/plans/{bugs-0915 → archive/2026-09-15-bugs-0915}/briefs/S2.md +0 -0
- /package/docs/plans/{bugs-0915 → archive/2026-09-15-bugs-0915}/briefs/S3.md +0 -0
- /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
|
+
}
|