poi-plugin-mcp 0.2.22 → 0.2.24

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,
@@ -13,6 +13,13 @@ function createPoiAutoInteractionRecorder(options = {}) {
13
13
  const remote = loadElectronRemote()
14
14
  return remote.webContents.fromId(webContentsId)
15
15
  })
16
+ // Remote-proxy memoization (0909 leak fix): every getWebContents() across
17
+ // @electron/remote registers fresh objects in poi's main-process
18
+ // ObjectsRegistry; at ~1 call/sec the per-context Map hits V8's size cap
19
+ // (RangeError: Map maximum size exceeded) and ALL game writes die until
20
+ // poi restarts. Resolve by integer id (no registration) and cache the
21
+ // proxy — Electron ids are monotonic, never reused after destroy.
22
+ const webContentsProxyCache = new Map()
16
23
  let captureScreenshot = options.captureScreenshot || null
17
24
  const createSessionRecorder = options.createSessionRecorder
18
25
  const eventTarget = options.eventTarget === undefined
@@ -63,12 +70,24 @@ function createPoiAutoInteractionRecorder(options = {}) {
63
70
  function currentWebContents() {
64
71
  const layout = getStore('layout.webview')
65
72
  if (!layout || !layout.ref) return null
66
- if (typeof layout.ref.getWebContents === 'function') {
67
- return layout.ref.getWebContents()
68
- }
69
73
  if (typeof layout.ref.getWebContentsId === 'function') {
70
74
  const id = layout.ref.getWebContentsId()
71
- if (Number.isInteger(id) && id > 0) return resolveWebContents(id)
75
+ if (Number.isInteger(id) && id > 0) {
76
+ const hit = webContentsProxyCache.get(id)
77
+ if (hit) return hit
78
+ const resolved = resolveWebContents(id)
79
+ if (resolved) {
80
+ webContentsProxyCache.set(id, resolved)
81
+ return resolved
82
+ }
83
+ }
84
+ }
85
+ if (typeof layout.ref.getWebContents === 'function') {
86
+ const webContents = layout.ref.getWebContents()
87
+ if (webContents) {
88
+ webContentsProxyCache.set('ref', webContents)
89
+ return webContents
90
+ }
72
91
  }
73
92
  return null
74
93
  }
@@ -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
@@ -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
  },