mercury-agent 0.8.6 → 0.8.8
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/package.json +1 -1
- package/src/core/runtime.ts +32 -16
- package/src/storage/pi-auth.ts +23 -0
package/package.json
CHANGED
package/src/core/runtime.ts
CHANGED
|
@@ -1288,12 +1288,43 @@ export class MercuryCoreRuntime {
|
|
|
1288
1288
|
// Container-relative workspace path
|
|
1289
1289
|
const containerWorkspace = `/spaces/${spaceId}`;
|
|
1290
1290
|
|
|
1291
|
+
// Resolve the reply target once — reused for isolation, context assembly,
|
|
1292
|
+
// and DB linkage. Non-null only when the quoted platform message is
|
|
1293
|
+
// recorded in THIS conversation's history (keyed by platform +
|
|
1294
|
+
// conversation + platform message id).
|
|
1295
|
+
let replyMercuryMsgId: number | null = null;
|
|
1296
|
+
if (
|
|
1297
|
+
replyMeta?.replyToPlatformMessageId &&
|
|
1298
|
+
replyMeta.platform &&
|
|
1299
|
+
replyMeta.conversationExternalId
|
|
1300
|
+
) {
|
|
1301
|
+
replyMercuryMsgId = this.db.lookupMercuryMessageId(
|
|
1302
|
+
replyMeta.platform,
|
|
1303
|
+
replyMeta.conversationExternalId,
|
|
1304
|
+
replyMeta.replyToPlatformMessageId,
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
const userReplyToId = replyMercuryMsgId ?? undefined;
|
|
1308
|
+
|
|
1291
1309
|
// ── Reply-chain isolation ──────────────────────────────────────────
|
|
1292
1310
|
// Strip quoted bot output from unprivileged group replies-to-bot
|
|
1293
1311
|
// BEFORE hooks see the prompt (defense in depth).
|
|
1312
|
+
//
|
|
1313
|
+
// Only isolate when the quoted message is NOT recorded in this
|
|
1314
|
+
// conversation's own history (replyMercuryMsgId === null). A recorded
|
|
1315
|
+
// match means the bot posted it publicly in this same conversation and
|
|
1316
|
+
// the member has already seen it — there is nothing to protect, so
|
|
1317
|
+
// isolating would only strip legitimate context. A miss means the quote
|
|
1318
|
+
// originated elsewhere (a DM, another space, or unverifiable content) —
|
|
1319
|
+
// keep isolating. Fail-safe: if outbound recording ever missed the
|
|
1320
|
+
// message, the lookup returns null and isolation still fires.
|
|
1294
1321
|
let replyIsolated = false;
|
|
1295
1322
|
let finalPrompt = prompt;
|
|
1296
|
-
if (
|
|
1323
|
+
if (
|
|
1324
|
+
replyFlags?.isReplyToBot &&
|
|
1325
|
+
!replyFlags.isDM &&
|
|
1326
|
+
replyMercuryMsgId === null
|
|
1327
|
+
) {
|
|
1297
1328
|
const seededAdmins = this.config.admins
|
|
1298
1329
|
? this.config.admins
|
|
1299
1330
|
.split(",")
|
|
@@ -1393,21 +1424,6 @@ export class MercuryCoreRuntime {
|
|
|
1393
1424
|
};
|
|
1394
1425
|
}
|
|
1395
1426
|
|
|
1396
|
-
// Resolve reply target once — reused for context assembly and DB linkage.
|
|
1397
|
-
let replyMercuryMsgId: number | null = null;
|
|
1398
|
-
if (
|
|
1399
|
-
replyMeta?.replyToPlatformMessageId &&
|
|
1400
|
-
replyMeta.platform &&
|
|
1401
|
-
replyMeta.conversationExternalId
|
|
1402
|
-
) {
|
|
1403
|
-
replyMercuryMsgId = this.db.lookupMercuryMessageId(
|
|
1404
|
-
replyMeta.platform,
|
|
1405
|
-
replyMeta.conversationExternalId,
|
|
1406
|
-
replyMeta.replyToPlatformMessageId,
|
|
1407
|
-
);
|
|
1408
|
-
}
|
|
1409
|
-
const userReplyToId = replyMercuryMsgId ?? undefined;
|
|
1410
|
-
|
|
1411
1427
|
const replyChainDepthStr = this.db.getSpaceConfig(
|
|
1412
1428
|
spaceId,
|
|
1413
1429
|
"context.reply_chain_depth",
|
package/src/storage/pi-auth.ts
CHANGED
|
@@ -68,6 +68,10 @@ export type PiAuthCredential =
|
|
|
68
68
|
/** An oauth entry exists but could not be turned into a usable key. */
|
|
69
69
|
| { status: "refresh-failed"; error?: Error };
|
|
70
70
|
|
|
71
|
+
// Deduplicate concurrent OAuth refreshes — refresh tokens are single-use, so
|
|
72
|
+
// two parallel calls with the same token race and one always fails.
|
|
73
|
+
const inflightRefresh = new Map<string, Promise<PiAuthCredential>>();
|
|
74
|
+
|
|
71
75
|
export async function getPiAuthCredential(options: {
|
|
72
76
|
provider: string;
|
|
73
77
|
authPath: string;
|
|
@@ -83,6 +87,25 @@ export async function getPiAuthCredential(options: {
|
|
|
83
87
|
return { status: "none" };
|
|
84
88
|
}
|
|
85
89
|
|
|
90
|
+
// Coalesce concurrent refreshes for the same auth file so only one
|
|
91
|
+
// token-endpoint call is made; the rest share its result.
|
|
92
|
+
const key = options.authPath;
|
|
93
|
+
const existing = inflightRefresh.get(key);
|
|
94
|
+
if (existing) return existing;
|
|
95
|
+
|
|
96
|
+
const promise = doGetPiAuthCredential(options);
|
|
97
|
+
inflightRefresh.set(key, promise);
|
|
98
|
+
try {
|
|
99
|
+
return await promise;
|
|
100
|
+
} finally {
|
|
101
|
+
inflightRefresh.delete(key);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function doGetPiAuthCredential(options: {
|
|
106
|
+
provider: string;
|
|
107
|
+
authPath: string;
|
|
108
|
+
}): Promise<PiAuthCredential> {
|
|
86
109
|
const authPath = options.authPath;
|
|
87
110
|
const auth = readAuthFile(authPath);
|
|
88
111
|
|