misakanet 2.43.1 → 2.44.1

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "misakanet",
3
- "version": "2.43.1",
3
+ "version": "2.44.1",
4
4
  "description": "Failure-memory network for AI coding agents: search evidence-rated failure lessons, submit new failures, and turn crashes into reusable knowledge.",
5
5
  "author": {
6
6
  "name": "Ikalus1988",
package/README.ja.md CHANGED
@@ -12,7 +12,7 @@ mcp-name: io.github.Ikalus1988/misakanet
12
12
  > 誰かがすでに代償を支払ったバグを、セッションごとに一つずつ再発見せずに済みます —— 上部の **Lessons**
13
13
  > バッジが現在のコーパス規模です。
14
14
  >
15
- > エージェントネイティブなインターフェース: [MCP サーバー](https://misakanet.org/mcp)(7 ツール)、WebMCP(ブラウザの
15
+ > エージェントネイティブなインターフェース: [MCP サーバー](https://misakanet.org/mcp)(8 ツール)、WebMCP(ブラウザの
16
16
  > `navigator.modelContext`)、`llms.txt` / `llms-full.txt`、そして `.well-known/agent-card.json` 経由の A2A ディスカバリー。
17
17
 
18
18
  <p align="center">
package/README.md CHANGED
@@ -12,7 +12,7 @@ mcp-name: io.github.Ikalus1988/misakanet
12
12
  > the bugs someone already paid for, instead of rediscovering them one session at a time — the **Lessons**
13
13
  > badge above is the live corpus size.
14
14
  >
15
- > Agent-native interfaces: [MCP server](https://misakanet.org/mcp) (7 tools), WebMCP (browser
15
+ > Agent-native interfaces: [MCP server](https://misakanet.org/mcp) (8 tools), WebMCP (browser
16
16
  > `navigator.modelContext`), `llms.txt` / `llms-full.txt`, and A2A discovery through
17
17
  > `.well-known/agent-card.json`.
18
18
 
package/README.zh-CN.md CHANGED
@@ -439,7 +439,7 @@ npx @misaka-net/misakanet-setup
439
439
 
440
440
  | 层 | 是什么 | 缺了它会怎样 |
441
441
  |---|---|---|
442
- | **① 服务** | `https://misakanet.org/mcp`(Streamable HTTP,7 个工具,匿名**不限次数**,只有反爬突发保护)或**本地 stdio**(clone 后 `python3 scripts/mcp_server.py`,无限额)| 没有可查的地方 |
442
+ | **① 服务** | `https://misakanet.org/mcp`(Streamable HTTP,8 个工具,匿名**不限次数**,只有反爬突发保护)或**本地 stdio**(clone 后 `python3 scripts/mcp_server.py`,无限额)| 没有可查的地方 |
443
443
  | **② 接入** | `npx @misaka-net/misakanet-setup`:把服务写进每个助手**自己的**配置文件(Claude Code / Codex / Hermes / OpenClaw / codewhale 各一套)| 你得自己知道 5 种配置文件分别怎么写 |
444
444
  | **③ 触发** | 规则块(「遇到报错先查经验库」)+ 检查点钩子(约 20 轮提醒沉淀)+ 14 天升级提示 | **端点在,但没有任何人会去调用它** |
445
445
 
@@ -452,8 +452,8 @@ setup 保证的是"工具确实在"和"该查的时刻更容易被抓住",不
452
452
 
453
453
  ### 装完你得到什么(逐条可自检)
454
454
 
455
- 1. **7 个 `misakanet_*` 工具出现在助手里** —— `codex mcp list` / `codewhale mcp tools` /
456
- `claude mcp list` / `hermes mcp list`;**证据**:列表里有 `misakanet` 且 7 个工具;
455
+ 1. **8 个 `misakanet_*` 工具出现在助手里** —— `codex mcp list` / `codewhale mcp tools` /
456
+ `claude mcp list` / `hermes mcp list`;**证据**:列表里有 `misakanet` 且 8 个工具;
457
457
  2. **助手被要求「遇错先查」** —— 问一句「switch vision model」「context window exceeded」这类片段,它应该先说查过经验库;
458
458
  **证据**:事件流里出现 `misakanet_search`(claude/codewhale 用 `--output-format stream-json`,codex 用 `--json`);
459
459
  3. **长会话会提醒沉淀** —— 约 20 轮后提醒把本次「失败 → 根因 → 修复 → 验证」变成一条课程
package/SKILL.md CHANGED
@@ -45,8 +45,8 @@ Do NOT use MisakaNet for:
45
45
  > stdio server adds `misakanet_me_events` as a **proxy** of the hosted tool, so the reuse-evidence
46
46
  > steps below work on either install.
47
47
  >
48
- > * **hosted** exposes 7 tools, `misakanet_me_events` among them;
49
- > * a **local stdio** install (`python3 scripts/mcp_server.py`) exposes those same 7 **plus three that
48
+ > * **hosted** exposes 8 tools, `misakanet_me_events` among them;
49
+ > * a **local stdio** install (`python3 scripts/mcp_server.py`) exposes those same 8 **plus three that
50
50
  > only make sense on your machine** — `misakanet_submit_usage`, `misakanet_usage_status` and
51
51
  > `misakanet_memory_context`. They read this checkout's own usage meter and `lessons/` corpus, which
52
52
  > a hosted endpoint has nothing to answer.
package/lib/client.js CHANGED
@@ -178,6 +178,16 @@ window.__ModuleLoader__.load({
178
178
  "panel.asked.voteNotHelpful": "not helpful",
179
179
  "panel.asked.voteNone": "not voted",
180
180
  "panel.asked.voteNoLesson": "no lesson",
181
+ "panel.asked.voteHint": "Same action as the verdict beside the assistant message, without scrolling back to it. Sends this lesson's id and one constant label (`dsh-web`); nothing else.",
182
+ "panel.asked.voteFailed": "could not reach misakanet.org — nothing was recorded",
183
+ "panel.asked.voteOne": "1 human confirmation — the reuse event now appears (E3: a reuse signal, not CI verification).",
184
+ "panel.asked.voteTwo": "2 human confirmations — enough for agents to read this as E4.",
185
+ "panel.asked.voteMore": "human confirmations — enough for agents to read this as E4.",
186
+ "panel.asked.noMatchTag": "no match",
187
+ "panel.asked.agentNext": "Hand this line to the agent:",
188
+ "panel.asked.agentNextCopy": "Copy the next step",
189
+ "panel.asked.agentNextText": "Please record this gap with the MisakaNet intake so the next person finds it: misakanet_submit_intake problem: ",
190
+ "panel.asked.agentNextWhy": "This page cannot file it: a report is re-checked by submitting the same text again, the server's dedup window is finite, and a page-triggered submit could open a second issue for one problem.",
181
191
  "panel.counters": "this session · counters local to this browser · no account, nothing uploaded but your votes and reports",
182
192
  "panel.report.title": "MisakaNet report",
183
193
  "panel.report.coveredBy": "covered by ",
@@ -196,6 +206,9 @@ window.__ModuleLoader__.load({
196
206
  "panel.trust.human": "human confirmation",
197
207
  "panel.trust.noLevel": "no level",
198
208
  "panel.trust.used": " · used ",
209
+ "panel.trust.external": "external reuse evidence: ",
210
+ "panel.trust.declared": "declared: ",
211
+ "panel.trust.e4signal": "live signal: E4",
199
212
  "panel.trust.footnote": "The level is the lesson's own stored evidence. The count is other people's confirmations, not yours: one makes the reuse event appear, the second is what agents read as E4.",
200
213
  "panel.activity.session": "this session: ",
201
214
  "panel.activity.browser": "on this browser: ",
@@ -212,9 +225,11 @@ window.__ModuleLoader__.load({
212
225
  "main.footnote": "The left column opens this page; the session-scoped counters live in the session's right-sidebar MisakaNet tab. The numbers here are what this browser has contributed.",
213
226
  "tool.rawResult": "raw result",
214
227
  "tool.readFailed": "result could not be read",
215
- "tool.helpfulTitle": "Sends this lesson's id to misakanet.org as reuse evidence. Sends nothing else.",
228
+ "tool.helpfulTitle": "Sends this lesson's id and the fixed label `dsh-web` to misakanet.org as reuse evidence. Sends nothing else.",
216
229
  "tool.notHelpfulTitle": "Sends this lesson's id and the search text, so the gap gets counted.",
217
- "tool.helpfulHint": "Helpful sends the lesson id · not-helpful also sends the search text (gap stats)",
230
+ "tool.helpfulHint": "Helpful sends the lesson id and the label `dsh-web` · not-helpful also sends the search text (gap stats)",
231
+ "tool.verdict.helpful": "recorded — this lesson now has {count} human confirmation(s)",
232
+ "tool.verdict.helpfulSelf": "recorded — this lesson now has your human confirmation",
218
233
  "tool.helpful": "👍 Helpful",
219
234
  "tool.notHelpful": "👎 Not what I needed",
220
235
  "tool.untitled": "(untitled)",
@@ -287,6 +302,16 @@ window.__ModuleLoader__.load({
287
302
  "panel.asked.voteNotHelpful": "没用",
288
303
  "panel.asked.voteNone": "未投票",
289
304
  "panel.asked.voteNoLesson": "无课程",
305
+ "panel.asked.voteHint": "与助手消息旁的裁决行是同一个动作,不用再往回滚。只发这篇课程的 id 和一个固定标签(`dsh-web`),不发别的。",
306
+ "panel.asked.voteFailed": "连不上 misakanet.org —— 这次没有记下任何东西",
307
+ "panel.asked.voteOne": "1 个人工确认 —— 复用事件现在出现了(E3:复用信号,不是 CI 验证)。",
308
+ "panel.asked.voteTwo": "2 个人工确认 —— 已经是 agent 读到的 E4 了。",
309
+ "panel.asked.voteMore": "个人工确认 —— 已经是 agent 读到的 E4 了。",
310
+ "panel.asked.noMatchTag": "无命中",
311
+ "panel.asked.agentNext": "把这行交给 agent:",
312
+ "panel.asked.agentNextCopy": "复制下一步",
313
+ "panel.asked.agentNextText": "请用 MisakaNet 报料记下这个缺口,让下一个人能搜到:misakanet_submit_intake problem: ",
314
+ "panel.asked.agentNextWhy": "这个页面不能替你报:报料的复查方式是原样再提交一次,而服务端的去重窗口是有限的 —— 页面触发的提交可能为同一个问题开出第二个 issue。",
290
315
  "panel.counters": "本会话 · 计数只存在本浏览器 · 没有账号,除了你的投票与报料,不上传任何东西",
291
316
  "panel.report.title": "MisakaNet 报料",
292
317
  "panel.report.coveredBy": "已被 ",
@@ -305,6 +330,9 @@ window.__ModuleLoader__.load({
305
330
  "panel.trust.human": "人工确认",
306
331
  "panel.trust.noLevel": "无等级",
307
332
  "panel.trust.used": " · 已复用 ",
333
+ "panel.trust.external": "外部复用证据:",
334
+ "panel.trust.declared": "课程自述:",
335
+ "panel.trust.e4signal": "实时信号:E4",
308
336
  "panel.trust.footnote": "等级来自课程自己存的证据;计数是别人的确认,不是你的:前者让复用事件出现,后者才是 agent 读到的 E4。",
309
337
  "panel.activity.session": "本会话:",
310
338
  "panel.activity.browser": "本浏览器:",
@@ -321,9 +349,11 @@ window.__ModuleLoader__.load({
321
349
  "main.footnote": "左栏打开的就是这一页;会话级的计数在右栏的 MisakaNet 标签里。这里的数字是本浏览器贡献的部分。",
322
350
  "tool.rawResult": "原始结果",
323
351
  "tool.readFailed": "结果读不出来",
324
- "tool.helpfulTitle": "把这篇课程的 id 发给 misakanet.org 作为复用证据,不发别的。",
352
+ "tool.helpfulTitle": "把这篇课程的 id 和一个固定标签(`dsh-web`)发给 misakanet.org 作为复用证据,不发别的。",
325
353
  "tool.notHelpfulTitle": "把课程 id 和检索文本一起发出去,好把缺口统计进去。",
326
- "tool.helpfulHint": "「有用」只发课程 id ·「没用」还会发检索文本(用于缺口统计)",
354
+ "tool.helpfulHint": "「有用」只发课程 id 与固定标签 `dsh-web` ·「没用」还会发检索文本(用于缺口统计)",
355
+ "tool.verdict.helpful": "已记录 —— 这篇课程现在有 {count} 个人工确认",
356
+ "tool.verdict.helpfulSelf": "已记录 —— 这篇课程现在有你的一次人工确认",
327
357
  "tool.helpful": "👍 有用",
328
358
  "tool.notHelpful": "👎 不是我需要的",
329
359
  "tool.untitled": "(无标题)",
@@ -590,6 +620,32 @@ var DEFAULT_MCP_URL = "https://misakanet.org/mcp";
590
620
  var OVERLAY_LIMIT = 5;
591
621
  var OVERLAY_TIMEOUT_MS = 8000;
592
622
 
623
+ /**
624
+ * The label every vote from this browser half carries: what cast it, not who.
625
+ *
626
+ * It is a **constant**, identical for every person and every session. There is no account, no
627
+ * `client_id`, no cookie and no generated value anywhere in this file — the repository's privacy
628
+ * line is "no account, no client_id, nothing uploaded but your votes and reports", and a fixed
629
+ * surface label does not cross it while a generated id would. What it buys is the only thing that
630
+ * was missing: `POST /api/helpful` used to accept `{lesson_id}` alone, so a vote cast from this
631
+ * panel was byte-identical to one cast by `curl` and the panel's own effect was unmeasurable by
632
+ * construction. The worker keeps `count` exactly as it was and writes this label to its own
633
+ * per-source, per-day counter.
634
+ */
635
+ var HELPFUL_SOURCE = "dsh-web";
636
+
637
+ /**
638
+ * The exact line the panel hands back to the agent for a search that matched nothing.
639
+ *
640
+ * The panel cannot file it: a receipt is pulled only by submitting the same text again, and the
641
+ * server's dedup window is finite, so a page-triggered submit could open a **second** issue for
642
+ * one problem (acceptance item D4). A copyable string has no such power, and it is the one thing
643
+ * this page can honestly offer — the agent in the chat is the only party that may submit.
644
+ */
645
+ function intakeNextStep(query) {
646
+ return T("panel.asked.agentNextText") + JSON.stringify(String(query || ""));
647
+ }
648
+
593
649
  function isJson(text) {
594
650
  try { JSON.parse(text); return true; } catch (error) { return false; }
595
651
  }
@@ -942,6 +998,23 @@ var DEFAULT_MCP_URL = "https://misakanet.org/mcp";
942
998
  });
943
999
  }
944
1000
 
1001
+ /**
1002
+ * The one place a helpful vote is posted, from either surface (the message verdict, the panel row).
1003
+ *
1004
+ * One function, so the two surfaces cannot drift into sending different bodies — and so the body
1005
+ * is stated once: this lesson's id, plus the constant `HELPFUL_SOURCE` label. No query text, no
1006
+ * identity, no timestamp of our own. The guard is a double-submit brake, not an auto-vote: it only
1007
+ * stops a *second* click on a sighting whose vote already landed, and abstaining remains free.
1008
+ */
1009
+ function sendHelpfulVote(log, entry) {
1010
+ if (!entry || entry.voted) return Promise.resolve(null);
1011
+ return post("/api/helpful", { lesson_id: entry.lessonId, source: HELPFUL_SOURCE })
1012
+ .then(function (answer) {
1013
+ recordVote(log, entry, "helpful", answer);
1014
+ return answer;
1015
+ });
1016
+ }
1017
+
945
1018
  /**
946
1019
  * The tool-call row for `misakanet_search`: what came back — and nothing else.
947
1020
  *
@@ -974,7 +1047,11 @@ var DEFAULT_MCP_URL = "https://misakanet.org/mcp";
974
1047
  } else if (payload && payload.no_match) {
975
1048
  head.push(h("span", { key: "state" }, " " + T("panel.asked.noMatch")));
976
1049
  } else if (results.length) {
977
- head.push(h("span", { key: "state" }, " " + results.length + (results.length === 1 ? " lesson" : " lessons")));
1050
+ // Through the plural helper, like every other count: this line used to spell the plural by
1051
+ // hand (`results.length === 1 ? " lesson" : " lessons"`), which is exactly the shape the
1052
+ // helper exists to prevent — and it was also the one count in this file that could not be
1053
+ // localized.
1054
+ head.push(h("span", { key: "state" }, " " + count(results.length, T("unit.lesson"), T("unit.lessons"))));
978
1055
  head.push(h("span", { key: "top", style: DIM }, " · top: "));
979
1056
  head.push(h("span", { key: "title" }, top.title || top.id || T("tool.untitled")));
980
1057
  // Only fields the DEFAULT payload actually carries. `detail: "compact"` is
@@ -1031,21 +1108,23 @@ var DEFAULT_MCP_URL = "https://misakanet.org/mcp";
1031
1108
  if (log.shownFor && log.shownFor !== messageId) return null;
1032
1109
 
1033
1110
  function send(kind) {
1034
- // 👍 carries the lesson id and nothing else. 👎 has to carry the search text: that is what
1035
- // turns one annoyed person into a counted gap, and the line below says so before the click.
1111
+ // 👍 carries the lesson id plus the fixed `dsh-web` label and nothing else — one call site
1112
+ // (`sendHelpfulVote`), shared with the panel's row so the two surfaces cannot drift. 👎 has
1113
+ // to carry the search text: that is what turns one annoyed person into a counted gap, and
1114
+ // the line below says so before the click.
1036
1115
  var call = kind === "helpful"
1037
- ? post("/api/helpful", { lesson_id: pending.lessonId })
1116
+ ? sendHelpfulVote(log, pending)
1038
1117
  : post("/api/feedback", { query: pending.query, lesson_id: pending.lessonId, feedback: "irrelevant" });
1039
1118
  call.then(
1040
1119
  function (answer) {
1041
- recordVote(log, pending, kind, answer);
1120
+ if (kind !== "helpful") recordVote(log, pending, kind, answer);
1042
1121
  setVerdict({
1043
1122
  kind: kind,
1044
1123
  count: answer && typeof answer.count === "number" ? answer.count : null,
1045
1124
  });
1046
1125
  },
1047
1126
  function () {
1048
- setVerdict({ error: "could not reach misakanet.org — nothing was recorded" });
1127
+ setVerdict({ error: T("panel.asked.voteFailed") });
1049
1128
  }
1050
1129
  );
1051
1130
  }
@@ -1056,7 +1135,9 @@ var DEFAULT_MCP_URL = "https://misakanet.org/mcp";
1056
1135
 
1057
1136
  if (verdict) {
1058
1137
  var done = verdict.kind === "helpful"
1059
- ? "recorded — this lesson now has " + (verdict.count === null ? "your" : verdict.count) + " human confirmation(s)"
1138
+ ? (verdict.count === null
1139
+ ? T("tool.verdict.helpfulSelf")
1140
+ : T("tool.verdict.helpful", { count: verdict.count }))
1060
1141
  : "recorded as not helpful — it feeds the unsolved-gap map";
1061
1142
  return h("span", { style: Object.assign({}, DIM, { fontSize: "12px" }) }, done);
1062
1143
  }
@@ -1151,6 +1232,11 @@ var DEFAULT_MCP_URL = "https://misakanet.org/mcp";
1151
1232
  var trustState = react.useState({});
1152
1233
  var trust = trustState[0];
1153
1234
  var setTrust = trustState[1];
1235
+ // What a click just did, per search row: the count the server answered with, or a failure. The
1236
+ // point of change #2 is that the click is *consequential*, so the row has to say what moved.
1237
+ var voteState = react.useState({});
1238
+ var panelVotes = voteState[0];
1239
+ var setPanelVotes = voteState[1];
1154
1240
 
1155
1241
  react.useEffect(function () {
1156
1242
  return subscribe(sessionId, function () {
@@ -1222,18 +1308,106 @@ var DEFAULT_MCP_URL = "https://misakanet.org/mcp";
1222
1308
  count(intakes.length, "report") + " (" + converted + " became a lesson)"));
1223
1309
 
1224
1310
  // ① what this session asked
1311
+ //
1312
+ // This is the session's own transcript, and each row already knows which searches were *not*
1313
+ // voted — so this is where a vote costs one click instead of a scroll back to the finalized
1314
+ // message the verdict lives beside. The row offers exactly the action the verdict row offers
1315
+ // (`sendHelpfulVote`, one call site), and abstaining stays free: the button is an offer, it is
1316
+ // not pre-selected and nothing posts without a click.
1317
+ //
1318
+ // A no-match row is drawn distinctly and carries the one thing this page may hand over: a
1319
+ // copyable line for the agent. It cannot file the gap itself (see `intakeNextStep`).
1320
+ function voteFromPanel(entry) {
1321
+ var key = "q" + entry.callId + entry.at;
1322
+ sendHelpfulVote(log, entry).then(
1323
+ function (answer) {
1324
+ var total = answer && typeof answer.count === "number" ? answer.count : null;
1325
+ if (total !== null) {
1326
+ // Written into the same per-lesson receipt section ③ renders, so the number the
1327
+ // click produced and the number the section shows can never disagree.
1328
+ asked.current[entry.lessonId] = true;
1329
+ setTrust(function (prev) {
1330
+ var next = Object.assign({}, prev);
1331
+ next[entry.lessonId] = total;
1332
+ return next;
1333
+ });
1334
+ }
1335
+ var said = total === null ? ""
1336
+ : (total === 1 ? T("panel.asked.voteOne")
1337
+ : (total === 2 ? T("panel.asked.voteTwo")
1338
+ : total + " " + T("panel.asked.voteMore")));
1339
+ setPanelVotes(function (prev) {
1340
+ var next = Object.assign({}, prev);
1341
+ next[key] = said || T("panel.asked.voteFailed");
1342
+ return next;
1343
+ });
1344
+ },
1345
+ function () {
1346
+ setPanelVotes(function (prev) {
1347
+ var next = Object.assign({}, prev);
1348
+ next[key] = T("panel.asked.voteFailed");
1349
+ return next;
1350
+ });
1351
+ }
1352
+ );
1353
+ }
1354
+
1225
1355
  rows.push(h("div", { key: "s1", style: { marginTop: "12px", fontWeight: 600 } }, T("panel.asked")));
1226
1356
  if (!searches.length) {
1227
1357
  rows.push(h("div", { key: "s1e", style: DIM }, T("panel.asked.none")));
1228
1358
  } else {
1229
1359
  var searchRows = searches.slice().reverse().slice(0, 20).map(function (entry) {
1230
- var vote = entry.voted ? (entry.voteKind === "helpful" ? "Helpful" : "not helpful") : (entry.lessonId ? "not voted" : "no lesson");
1231
- return h("div", { key: "q" + entry.callId + entry.at, style: { borderTop: "1px solid " + MUTED, padding: "4px 0" } },
1232
- h("span", { style: DIM }, clockOf(entry.at) + " "),
1233
- h("span", {}, entry.query || T("panel.asked.noQuery")),
1234
- h("span", { style: DIM }, " " + (entry.noMatch ? T("panel.asked.nothingFound") : count(entry.hits, T("unit.lesson")))),
1235
- entry.title ? h("span", { style: DIM }, " · top: " + entry.title + (entry.evidence ? " (" + entry.evidence + ")" : "")) : null,
1236
- h("span", { style: DIM }, " · " + vote));
1360
+ var key = "q" + entry.callId + entry.at;
1361
+ var vote = entry.voted
1362
+ ? (entry.voteKind === "helpful" ? T("panel.asked.voteHelpful") : T("panel.asked.voteNotHelpful"))
1363
+ : (entry.lessonId ? T("panel.asked.voteNone") : T("panel.asked.voteNoLesson"));
1364
+ var said = panelVotes[key];
1365
+ var missed = Boolean(entry.noMatch) || !entry.lessonId;
1366
+ var controls = [];
1367
+ if (entry.lessonId && !entry.voted) {
1368
+ controls.push(h("button", {
1369
+ key: "v", type: "button", style: BUTTON, title: T("panel.asked.voteHint"),
1370
+ onClick: function () { voteFromPanel(entry); },
1371
+ }, T("tool.helpful")));
1372
+ }
1373
+ if (missed) {
1374
+ // A copy, never a submit. The string is the next step *for the agent*, because the
1375
+ // only way to pull a receipt is to submit the same text again and a page-triggered
1376
+ // re-check could open a second issue for one problem.
1377
+ controls.push(h("button", {
1378
+ key: "c", type: "button", style: BUTTON, title: intakeNextStep(entry.query),
1379
+ onClick: function () {
1380
+ copyText(intakeNextStep(entry.query), function (label) {
1381
+ setPanelVotes(function (prev) {
1382
+ var next = Object.assign({}, prev);
1383
+ next[key] = label;
1384
+ return next;
1385
+ });
1386
+ });
1387
+ },
1388
+ }, T("panel.asked.agentNextCopy")));
1389
+ }
1390
+ var lines = [
1391
+ h("div", { key: "head", style: { borderLeft: "2px solid " + (missed ? "rgba(127,127,127,0.9)" : "transparent"), paddingLeft: missed ? "6px" : "8px" } },
1392
+ h("span", { style: DIM }, clockOf(entry.at) + " "),
1393
+ h("span", {}, entry.query || T("panel.asked.noQuery")),
1394
+ h("span", { style: DIM }, " " + (entry.noMatch ? T("panel.asked.nothingFound") : count(entry.hits, T("unit.lesson")))),
1395
+ missed ? h("span", { style: Object.assign({}, DIM, { border: "1px solid " + MUTED, borderRadius: "4px", padding: "0 4px", marginLeft: "4px", fontSize: "11px" }) },
1396
+ T("panel.asked.noMatchTag")) : null,
1397
+ entry.title ? h("span", { style: DIM }, " · top: " + entry.title + (entry.evidence ? " (" + entry.evidence + ")" : "")) : null,
1398
+ entry.lessonId ? h("span", { style: DIM }, " · " + vote) : null),
1399
+ ];
1400
+ if (missed) {
1401
+ lines.push(h("div", { key: "next", style: DIM },
1402
+ T("panel.asked.agentNext") + " ",
1403
+ h("code", { style: { fontSize: "11px" } }, intakeNextStep(entry.query))));
1404
+ lines.push(h("div", { key: "why", style: DIM }, T("panel.asked.agentNextWhy")));
1405
+ }
1406
+ if (controls.length || said) {
1407
+ lines.push(h("div", { key: "do" }, controls,
1408
+ said ? h("span", { style: DIM }, said) : null));
1409
+ }
1410
+ return h("div", { key: key, style: { borderTop: "1px solid " + MUTED, padding: "4px 0" } }, lines);
1237
1411
  });
1238
1412
  rows.push(h("div", { key: "s1r" }, searchRows));
1239
1413
  }
@@ -1272,6 +1446,24 @@ var DEFAULT_MCP_URL = "https://misakanet.org/mcp";
1272
1446
  }
1273
1447
 
1274
1448
  // ③ how much these lessons are trusted
1449
+ //
1450
+ // Two different facts, kept apart because they come from two different places and mean two
1451
+ // different things:
1452
+ // * **declared** — the lesson's own stored `evidence_level`, from the compact search payload;
1453
+ // * **external reuse evidence** — the confirmation count `GET /api/helpful?lesson_id=` returns,
1454
+ // i.e. other people's votes, not yours, which is the live signal `misakanet_me_events`
1455
+ // reports.
1456
+ //
1457
+ // What this panel canNOT show, stated here rather than implied: `misakanet_me_events` returns a
1458
+ // richer receipt (per-event types, `count`/`queries`/`sources`, its own `evidence`) but it is an
1459
+ // MCP tool, reachable only through the MCP endpoint with a JSON-RPC `tools/call` — and this
1460
+ // browser half is forbidden from making one (acceptance D4: the same endpoint files issues). The
1461
+ // count therefore carries the helpful-vote signal, and the regression citations and cross-node
1462
+ // confirmations the tool also aggregates are **not** rendered. Nothing here is guessed: no field
1463
+ // this endpoint does not return is displayed.
1464
+ //
1465
+ // The count itself is the same threshold `misakanet_me_events` reports: one helpful vote is a
1466
+ // reuse signal (E3), two or more is E4 — which is also why the rule below is stated once.
1275
1467
  rows.push(h("div", { key: "s3", style: { marginTop: "12px", fontWeight: 600 } }, T("panel.trust")));
1276
1468
  if (!lessons.length) {
1277
1469
  rows.push(h("div", { key: "s3e", style: DIM }, T("panel.trust.none")));
@@ -1285,10 +1477,15 @@ var DEFAULT_MCP_URL = "https://misakanet.org/mcp";
1285
1477
  var counted = confirmations === undefined ? T("panel.trust.checking")
1286
1478
  : (confirmations === null ? T("panel.trust.unavailable")
1287
1479
  : count(confirmations, T("panel.trust.human")) + (confirmations >= 2 ? " → E4" : ""));
1480
+ var declared = lesson.evidence || T("panel.trust.noLevel");
1481
+ var external = h("span", { style: DIM },
1482
+ T("panel.trust.external") + counted + (confirmations >= 2 ? " · " + T("panel.trust.e4signal") : ""));
1288
1483
  return h("div", { key: "l" + lesson.lessonId, style: { borderTop: "1px solid " + MUTED, padding: "4px 0" } },
1289
- h("span", {}, lesson.title || lesson.lessonId),
1290
- h("span", { style: DIM }, " " + (lesson.evidence || T("panel.trust.noLevel")) + " · " + counted +
1291
- T("panel.trust.used") + lesson.count + "× · " + timeRange(lesson)));
1484
+ h("div", null,
1485
+ h("span", {}, lesson.title || lesson.lessonId),
1486
+ h("span", { style: DIM }, " " + T("panel.trust.declared") + declared +
1487
+ T("panel.trust.used") + lesson.count + "× · " + timeRange(lesson))),
1488
+ h("div", null, external));
1292
1489
  })));
1293
1490
  }
1294
1491
 
@@ -2120,6 +2317,11 @@ var DEFAULT_MCP_URL = "https://misakanet.org/mcp";
2120
2317
  }
2121
2318
 
2122
2319
  exports.apply = apply;
2320
+ // Test hooks, not surfaces: the headless render (`tests/test_dsh_plugin_panel_render.py`) has to
2321
+ // seed the session log exactly the way the transcript rows do, or the panel it renders is an empty
2322
+ // panel that proves nothing. Same constructor, same recorder, no second shape to drift.
2323
+ exports.__sessionLog = sessionLog;
2324
+ exports.__recordSearch = recordSearch;
2123
2325
  exports.inject = inject;
2124
2326
  exports.name = "misakanet-client";
2125
2327
  return module.exports;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "misakanet",
3
- "version": "2.43.1",
3
+ "version": "2.44.1",
4
4
  "license": "Apache-2.0",
5
5
  "description": "MisakaNet failure-memory skill — evidence-rated failure lessons for AI agents. DSH bundle: registers the MCP tools as mcp__misakanet__* (misakanet_search/get_lesson/…), served by the public Streamable HTTP endpoint https://misakanet.org/mcp — live tools from an npm install, in any profile, with no local python. A profile that prefers the repo's own stdio server can override the bundle row.",
6
6
  "icon": "icon.svg",
@@ -45,8 +45,8 @@ Do NOT use MisakaNet for:
45
45
  > stdio server adds `misakanet_me_events` as a **proxy** of the hosted tool, so the reuse-evidence
46
46
  > steps below work on either install.
47
47
  >
48
- > * **hosted** exposes 7 tools, `misakanet_me_events` among them;
49
- > * a **local stdio** install (`python3 scripts/mcp_server.py`) exposes those same 7 **plus three that
48
+ > * **hosted** exposes 8 tools, `misakanet_me_events` among them;
49
+ > * a **local stdio** install (`python3 scripts/mcp_server.py`) exposes those same 8 **plus three that
50
50
  > only make sense on your machine** — `misakanet_submit_usage`, `misakanet_usage_status` and
51
51
  > `misakanet_memory_context`. They read this checkout's own usage meter and `lessons/` corpus, which
52
52
  > a hosted endpoint has nothing to answer.