poi-plugin-mcp 0.2.21 → 0.2.23

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/index.js CHANGED
@@ -5,6 +5,7 @@ const { createSettingsClass } = require('./lib/settings-view')
5
5
  const telemetry = createPoiTelemetry()
6
6
  const controller = createBridgeController({
7
7
  getQuestList: telemetry.getQuestList,
8
+ getMissionBoard: telemetry.getMissionBoard,
8
9
  getQuestAction: telemetry.getQuestAction,
9
10
  getEquipmentAction: telemetry.getEquipmentAction,
10
11
  getEquipmentSelection: telemetry.getEquipmentSelection,
@@ -32,6 +32,7 @@ function createBridgeController(options = {}) {
32
32
  captureScreenshot: options.captureScreenshot,
33
33
  performInput: options.performInput,
34
34
  getQuestList: options.getQuestList,
35
+ getMissionBoard: options.getMissionBoard,
35
36
  getQuestAction: options.getQuestAction,
36
37
  getEquipmentAction: options.getEquipmentAction,
37
38
  getEquipmentSelection: options.getEquipmentSelection,
@@ -71,6 +71,8 @@ function createPoiDataBridge(options = {}) {
71
71
  const masterFile = options.masterFile || DEFAULT_MASTER_FILE
72
72
  const logger = options.logger || console
73
73
  const getQuestList = options.getQuestList || (() => ({ available: false, generation: 0 }))
74
+ const getMissionBoard = options.getMissionBoard ||
75
+ (() => ({ available: false, generation: 0 }))
74
76
  const getQuestAction = options.getQuestAction || (() => ({ available: false, generation: 0 }))
75
77
  const getEquipmentAction = options.getEquipmentAction ||
76
78
  (() => ({ available: false, generation: 0 }))
@@ -591,6 +593,11 @@ function createPoiDataBridge(options = {}) {
591
593
  return
592
594
  }
593
595
 
596
+ if (endpoint === '/mission-board') {
597
+ sendJson(res, 200, getMissionBoard())
598
+ return
599
+ }
600
+
594
601
  if (endpoint === '/quest-action') {
595
602
  sendJson(res, 200, getQuestAction())
596
603
  return
package/lib/poi-input.js CHANGED
@@ -32,12 +32,14 @@ function createPoiInputProvider(options = {}) {
32
32
  return async function performPoiInput(operation) {
33
33
  validateOperationObject(operation)
34
34
  const layout = readLayout(getStore, resolveWebContents)
35
- // The game's PIXI interaction manager drops synthetic pointer events while
36
- // its document lacks focus (verified live 2026-08-17: clicks reached the
37
- // canvas yet produced no game/API effect until a real OS-level click gave
38
- // the WebView focus). Focus the hosting window first: webContents.focus()
39
- // alone cannot give the document OS focus while the window is backgrounded.
40
- focusGameWindow(layout.webContents)
35
+ // Live 2026-08-24: synthetic clicks reach the game and take effect in a
36
+ // visible, non-focused window; they only stall when the window is
37
+ // minimized/occluded and Chromium throttles the renderer (PIXI ticker
38
+ // frozen). So: un-throttle the webContents, restore a minimized window,
39
+ // and keep the document focused at the webContents level — never steal
40
+ // the user's OS foreground (the Windows foreground lock silently ignored
41
+ // the old win.focus() anyway).
42
+ prepareGameWindowForInput(layout.webContents)
41
43
 
42
44
  switch (operation.operation) {
43
45
  case 'move':
@@ -84,17 +86,23 @@ function validateOperationObject(operation) {
84
86
  }
85
87
  }
86
88
 
87
- function focusGameWindow(webContents) {
89
+ function prepareGameWindowForInput(webContents) {
90
+ try {
91
+ if (typeof webContents.setBackgroundThrottling === 'function') {
92
+ webContents.setBackgroundThrottling(false)
93
+ }
94
+ } catch (_throttleError) {
95
+ // Throttling control is best-effort; input below is still delivered.
96
+ }
88
97
  try {
89
98
  const { BrowserWindow } = require('electron')
90
99
  const win = BrowserWindow.fromWebContents(
91
100
  webContents.hostWebContents || webContents,
92
101
  )
93
- if (win && !win.isFocused()) {
94
- // show-no-activate keeps the user's foreground window; focus() then
95
- // grants the document the OS focus the game's input layer requires.
96
- if (win.isMinimized()) win.restore()
97
- win.focus()
102
+ if (win && win.isMinimized()) {
103
+ // Restoring resumes the throttled renderer; it does not need the
104
+ // OS foreground for synthetic input to work (verified 2026-08-24).
105
+ win.restore()
98
106
  }
99
107
  } catch (_windowError) {
100
108
  // Fall through to webContents-level focus below.
@@ -21,8 +21,8 @@ const DEFAULT_MAX_TOTAL_RECORDING_BYTES = 64 * 1024 * 1024 * 1024
21
21
  const DEFAULT_FINAL_MANIFEST_RESERVE_BYTES = 64 * 1024
22
22
  const DEFAULT_CHECKPOINT_DELAYS_MS = Object.freeze([0, 250, 1000])
23
23
  const DEFAULT_OUTPUT_ROOT = process.platform === 'win32'
24
- ? 'D:\\poi-mcp\\recordings'
25
- : path.join(os.homedir(), '.poi-mcp', 'recordings')
24
+ ? (process.env.POI_MCP_RECORDING_DIR || 'E:\\kancolle\\recordings')
25
+ : (process.env.POI_MCP_RECORDING_DIR || path.join(os.homedir(), '.poi-mcp', 'recordings'))
26
26
  const SENSITIVE_KEY = /(auth|authorization|cookie|credential|key|login(?:data)?|password|secret|session|sid|ticket|token)/iu
27
27
  const SENSITIVE_VALUE = /\b(?:basic|bearer|api[_-]?token|access[_-]?token|refresh[_-]?token|session[_-]?id)\b/iu
28
28
  const GAME_BUSINESS_SORT_KEYS = new Set([
@@ -2,6 +2,7 @@ const { createPoiActionEvents } = require('./poi-action-events')
2
2
  const { createPoiApiResponses } = require('./poi-api-responses')
3
3
 
4
4
  const QUEST_LIST_PATH = '/kcsapi/api_get_member/questlist'
5
+ const MISSION_BOARD_PATH = '/kcsapi/api_get_member/mission'
5
6
  const UNSET_SLOT_PATH = '/kcsapi/api_get_member/unsetslot'
6
7
  const EQUIPMENT_SELECTION_PATH = '/kcsapi/api_req_kaisou/slot_select'
7
8
  const QUEST_ACTION_PATHS = new Set([
@@ -50,6 +51,8 @@ function createPoiTelemetry(options = {}) {
50
51
  const apiResponses = createPoiApiResponses({ now })
51
52
  let questGeneration = 0
52
53
  let questList = null
54
+ let missionBoardGeneration = 0
55
+ let missionBoard = null
53
56
  let questActionGeneration = 0
54
57
  let questAction = null
55
58
  let equipmentActionGeneration = 0
@@ -73,6 +76,10 @@ function createPoiTelemetry(options = {}) {
73
76
  captureQuestList(detail, actionEvent)
74
77
  return
75
78
  }
79
+ if (detail.path === MISSION_BOARD_PATH) {
80
+ captureMissionBoard(detail, actionEvent)
81
+ return
82
+ }
76
83
  if (detail.path === UNSET_SLOT_PATH) {
77
84
  captureUnsetSlot(detail, actionEvent)
78
85
  return
@@ -140,6 +147,40 @@ function createPoiTelemetry(options = {}) {
140
147
  }
141
148
  }
142
149
 
150
+ function captureMissionBoard(detail, actionEvent) {
151
+ const body = detail.body
152
+ if (!body || typeof body !== 'object' || Array.isArray(body)) return
153
+ const source = body.api_data &&
154
+ typeof body.api_data === 'object' &&
155
+ !Array.isArray(body.api_data)
156
+ ? body.api_data
157
+ : body
158
+ if (!Array.isArray(source.api_list_items)) return
159
+
160
+ const items = source.api_list_items.flatMap((item) => {
161
+ const missionId = positiveIntegerOrNull(item && item.api_mission_id)
162
+ const state = toInteger(item && item.api_state)
163
+ if (missionId == null || state == null || state < 0) return []
164
+ return [{ missionId, state }]
165
+ })
166
+ if (items.length === 0) return
167
+
168
+ missionBoardGeneration += 1
169
+ missionBoard = {
170
+ available: true,
171
+ generation: missionBoardGeneration,
172
+ capturedAt: actionEvent
173
+ ? actionEvent.capturedAt
174
+ : now().toISOString(),
175
+ items,
176
+ limitTime: positiveIntegerOrNull(
177
+ Array.isArray(source.api_limit_time)
178
+ ? source.api_limit_time[0]
179
+ : source.api_limit_time,
180
+ ),
181
+ }
182
+ }
183
+
143
184
  function captureQuestAction(detail, actionEvent) {
144
185
  const apiResult = actionEvent
145
186
  ? actionEvent.apiResult
@@ -303,6 +344,9 @@ function createPoiTelemetry(options = {}) {
303
344
  getQuestList() {
304
345
  return questList || { available: false, generation: 0 }
305
346
  },
347
+ getMissionBoard() {
348
+ return missionBoard || { available: false, generation: 0 }
349
+ },
306
350
  getQuestAction() {
307
351
  return questAction || { available: false, generation: 0 }
308
352
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
- {
1
+ {
2
2
  "name": "poi-plugin-mcp",
3
- "version": "0.2.21",
3
+ "version": "0.2.23",
4
4
  "description": "Poi data, WebView capture, and opt-in authenticated input bridge for local KanColle tools.",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -22,7 +22,7 @@
22
22
  "test": "node --test test/*.test.js"
23
23
  },
24
24
  "poiPlugin": {
25
- "title": "MCP 数据桥",
25
+ "title": "MCP 数据桥",
26
26
  "id": "mcp_data_bridge",
27
27
  "description": "Expose Poi state, in-memory game WebView captures, and opt-in authenticated input on a local bridge.",
28
28
  "icon": "fa/database",