Skip to content

Commit 08c481c

Browse files
committed
fix: strict posts --mine attribution + diagnostic (MB-303)
- Use /agents/me canonical name for --mine identity resolution - Query /agents/profile with single correct field (name) - Detect attribution mismatch: warn when profile shows 0 posts but global feed has matching author.name entries - Read author.name (nested) not author_name (flat) per actual API shape - Strip back-compat shotgun queries; one path per field
1 parent 1fc7b34 commit 08c481c

File tree

1 file changed

+81
-28
lines changed

1 file changed

+81
-28
lines changed

src/commands/posts.ts

Lines changed: 81 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ import { printError, printInfo, printJson } from "../lib/output";
55
import { recordPost, recordRequest } from "../lib/rate_limit";
66
import { scanOutbound } from "../lib/safety";
77

8+
type MineDiagnostic = {
9+
warning: string;
10+
matched_posts: number;
11+
author_name?: string;
12+
sample_post_ids: string[];
13+
};
14+
815
export function registerPostCommands(ctx: CommandContext): void {
916
const {
1017
program,
@@ -32,48 +39,82 @@ export function registerPostCommands(ctx: CommandContext): void {
3239
.option("--cursor <cursor>", "Pagination cursor")
3340
.action(async (cmd) => {
3441
const opts = globals();
35-
const { client, profile } = buildClient(true);
42+
const { client } = buildClient(true);
3643
let res;
44+
let mineDiagnostic: MineDiagnostic | undefined;
3745

3846
if (cmd.mine) {
39-
const storedName =
40-
profile && typeof (profile as Record<string, unknown>).agent_name === "string"
41-
? ((profile as Record<string, unknown>).agent_name as string)
42-
: undefined;
43-
44-
let agentName = storedName;
45-
if (!agentName) {
46-
const meRes = await request(client, "GET", "/agents/me", { idempotent: true });
47-
if (!meRes.ok) {
48-
printError(
49-
`Posts list (mine) failed to resolve agent name (${meRes.status}): ${meRes.error || "unknown error"}`,
50-
opts,
51-
);
52-
process.exit(1);
53-
}
54-
const payload = meRes.data as Record<string, unknown> | undefined;
55-
agentName =
56-
typeof payload?.name === "string"
57-
? payload.name
58-
: payload &&
59-
typeof payload?.agent === "object" &&
60-
payload.agent &&
61-
typeof (payload.agent as Record<string, unknown>).name === "string"
62-
? ((payload.agent as Record<string, unknown>).name as string)
63-
: undefined;
47+
const meRes = await request(client, "GET", "/agents/me", { idempotent: true });
48+
if (!meRes.ok) {
49+
printError(
50+
`Posts list (mine) failed to resolve agent name (${meRes.status}): ${meRes.error || "unknown error"}`,
51+
opts,
52+
);
53+
process.exit(1);
6454
}
6555

56+
const mePayload =
57+
meRes.data && typeof meRes.data === "object"
58+
? (meRes.data as Record<string, unknown>)
59+
: undefined;
60+
const agentName = typeof mePayload?.name === "string" ? mePayload.name : undefined;
61+
6662
if (!agentName) {
6763
printError(
68-
"Posts list (mine) requires a known agent name. Re-run 'mb register' or ensure the profile has agent_name stored.",
64+
"Posts list (mine) requires a known agent name from /agents/me. Re-run 'mb register' and verify 'mb whoami'.",
6965
opts,
7066
);
7167
process.exit(1);
7268
}
7369

7470
res = await request(client, "GET", "/agents/profile", {
7571
query: { name: agentName },
72+
idempotent: true,
7673
});
74+
75+
if (res.ok) {
76+
const profilePayload =
77+
res.data && typeof res.data === "object" ? (res.data as Record<string, unknown>) : {};
78+
const recentPosts = Array.isArray(profilePayload.recentPosts)
79+
? profilePayload.recentPosts
80+
: [];
81+
82+
if (recentPosts.length === 0) {
83+
const globalRes = await request(client, "GET", "/posts", {
84+
query: { sort: "new", limit: Math.max(20, Number(cmd.limit) || 20) },
85+
idempotent: true,
86+
});
87+
88+
if (globalRes.ok) {
89+
const globalPayload =
90+
globalRes.data && typeof globalRes.data === "object"
91+
? (globalRes.data as Record<string, unknown>)
92+
: {};
93+
const globalPosts = Array.isArray(globalPayload.posts) ? globalPayload.posts : [];
94+
95+
const ownedPosts = globalPosts.filter((post): post is Record<string, unknown> => {
96+
if (!post || typeof post !== "object") return false;
97+
const author = (post as Record<string, unknown>).author;
98+
if (!author || typeof author !== "object") return false;
99+
const authorName = (author as Record<string, unknown>).name;
100+
return typeof authorName === "string" && authorName.toLowerCase() === agentName.toLowerCase();
101+
});
102+
103+
if (ownedPosts.length > 0) {
104+
mineDiagnostic = {
105+
warning:
106+
"Mine/profile attribution mismatch: profile shows no recent posts, but global posts contains entries for this agent.",
107+
matched_posts: ownedPosts.length,
108+
author_name: agentName,
109+
sample_post_ids: ownedPosts
110+
.map((post) => (typeof post.id === "string" ? post.id : undefined))
111+
.filter((id): id is string => !!id)
112+
.slice(0, 5),
113+
};
114+
}
115+
}
116+
}
117+
}
77118
} else {
78119
res = await request(client, "GET", "/posts", {
79120
query: { sort: cmd.sort, submolt: cmd.submolt, limit: cmd.limit, cursor: cmd.cursor },
@@ -87,12 +128,24 @@ export function registerPostCommands(ctx: CommandContext): void {
87128

88129
const { data, safety, sanitization } = await attachInboundSafety(res.data);
89130
if (opts.json) {
90-
printJson({ result: data, safety, sanitization, mode: cmd.mine ? "mine" : "all" });
131+
printJson({
132+
result: data,
133+
safety,
134+
sanitization,
135+
mode: cmd.mine ? "mine" : "all",
136+
diagnostics: mineDiagnostic,
137+
});
91138
return;
92139
}
93140

94141
if (cmd.mine) {
95142
printInfo("Note: showing agent profile response with recentPosts.", opts);
143+
if (mineDiagnostic) {
144+
printInfo(
145+
`Warning: ${mineDiagnostic.warning} Matched posts in global feed: ${mineDiagnostic.matched_posts}.`,
146+
opts,
147+
);
148+
}
96149
}
97150
warnSanitization(sanitization, opts, "sanitized inbound posts content");
98151
if (safety.length > 0) {

0 commit comments

Comments
 (0)