Browse Source

feat-chat-logger-main-world-stage1

master
Serge Shutov 2 weeks ago
parent
commit
c8f18fa447
  1. 1
      .gitignore
  2. 28
      bridge/server.js
  3. 43
      content/chat-logger.js
  4. 183
      injected/chat-logger.js
  5. 15
      manifest.json
  6. 2
      memory/state.json

1
.gitignore vendored

@ -2,3 +2,4 @@ node_modules/ @@ -2,3 +2,4 @@ node_modules/
.dsh-pro/
.vscode/
*.log
memory/chat-log/

28
bridge/server.js

@ -225,7 +225,33 @@ async function handle(req, res, pathname, body) { @@ -225,7 +225,33 @@ async function handle(req, res, pathname, body) {
}
throw new ToolError('E_ARGS_INVALID', 'GET or PUT only');
}
default:
case '/log/append': {
if (req.method !== 'POST') throw new ToolError('E_ARGS_INVALID', 'only POST');
const file = body && body.file;
const lines = body && body.lines;
if (typeof file !== 'string') throw new ToolError('E_ARGS_INVALID', 'file required');
if (file.length < 7 || file.length > 44) throw new ToolError('E_ARGS_INVALID', 'file length');
if (file.slice(-6) !== '.jsonl') throw new ToolError('E_ARGS_INVALID', 'file ext');
if (file.indexOf('/') !== -1) throw new ToolError('E_ARGS_INVALID', 'file slash');
if (file.indexOf(String.fromCharCode(92)) !== -1) throw new ToolError('E_ARGS_INVALID', 'file backslash');
if (!Array.isArray(lines) || lines.length === 0) throw new ToolError('E_ARGS_INVALID', 'lines required');
if (lines.length > 200) throw new ToolError('E_ARGS_INVALID', 'too many lines');
const logDir = path.join(resolveMemoryDir(config, BRIDGE_DIR), 'chat-log');
fs.mkdirSync(logDir, { recursive: true });
const target = path.join(logDir, file);
const resolved = path.resolve(target);
const rootResolved = path.resolve(logDir) + path.sep;
if (!resolved.startsWith(rootResolved)) throw new ToolError('E_PATH_ESCAPE', 'escape');
const buf = lines.map(function (l) {
const s = typeof l === 'string' ? l : JSON.stringify(l);
return s + String.fromCharCode(10);
}).join('');
const bytes = Buffer.byteLength(buf, 'utf8');
if (bytes > 524288) throw new ToolError('E_ARGS_INVALID', 'payload too big');
fs.appendFileSync(resolved, buf, 'utf8');
return { ok: true, data: { file: file, lines: lines.length, bytes: bytes } };
}
default:
// /handoff/:runId 与 /handoff/:runId/cancel
const m = pathname.match(/^\/handoff\/([A-Za-z0-9-]+)(\/cancel)?$/);
if (m) {

43
content/chat-logger.js

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
'use strict';
// ISOLATED world: инжект MAIN-world chat-logger и передача конфига через CustomEvent
(function () {
var TOKEN_KEY = 'dshmem-bridge-token'
var ENABLED_KEY = 'dshmem-log-enabled'
var HOOK_ID = 'dshmem-hook'
function pushConfig() {
try {
chrome.storage.local.get([TOKEN_KEY, ENABLED_KEY], function (v) {
var detail = {
token: v && v[TOKEN_KEY] ? String(v[TOKEN_KEY]) : '',
enabled: !(v && v[ENABLED_KEY] === false)
}
try {
window.dispatchEvent(new CustomEvent('dshmem:log-config', { detail: JSON.stringify(detail) }))
} catch (e) { }
})
} catch (e) { }
}
function injectHook() {
if (document.getElementById(HOOK_ID)) return
var script = document.createElement('script')
script.id = HOOK_ID
script.src = chrome.runtime.getURL('injected/chat-logger.js')
script.async = false
script.onload = function () { script.remove() }
;(document.head || document.documentElement).appendChild(script)
}
window.addEventListener('dshmem:log-request', pushConfig)
try {
chrome.storage.onChanged.addListener(function (changes, area) {
if (area !== 'local') return
if (changes[TOKEN_KEY] || changes[ENABLED_KEY]) pushConfig()
})
} catch (e) { }
injectHook()
console.log('[chat-log] injected')
})()

183
injected/chat-logger.js

@ -0,0 +1,183 @@ @@ -0,0 +1,183 @@
'use strict';
(function () {
var TOKEN_KEY = 'dshmem-bridge-token'
var ENABLED_KEY = 'dshmem-log-enabled'
var BASE = 'http' + ':' + '//' + '127.0.0.1' + ':' + '8766'
var CHUNK = 700
var seen = Object.create(null)
var cfg = null
var SEP = String.fromCharCode(10) + String.fromCharCode(10)
function hash(s) {
var h1 = 5381
var h2 = 52711
for (var i = 0; i !== s.length; i++) {
var c = s.charCodeAt(i)
h1 = (((h1 * 33) ^ c) >>> 0)
h2 = (((h2 * 31) ^ c) >>> 0)
}
return h1.toString(36) + h2.toString(36)
}
function today() {
var d = new Date()
var m = String(d.getMonth() + 1)
var dd = String(d.getDate())
if (m.length === 1) m = '0' + m
if (dd.length === 1) dd = '0' + dd
return d.getFullYear() + '-' + m + '-' + dd
}
function chunk(text) {
var out = []
var n = Math.ceil(text.length / CHUNK) || 1
for (var i = 0; i !== n; i++) out.push(text.slice(i * CHUNK, (i + 1) * CHUNK))
return out
}
function isInj(text) {
if (text.indexOf('MEMORY_SYSTEM') !== -1) return true
if (text.indexOf('DSM:memory_calls') !== -1) return true
if (text.indexOf('DSM:memory_write') !== -1) return true
if (text.indexOf('dsmemory') !== -1) return true
return false
}
function linesFor(mid, role, text, ts) {
var cs = chunk(text)
var arr = []
for (var i = 0; i !== cs.length; i++) {
var obj = {
mid: mid,
part: i,
total: cs.length,
role: role,
ts: ts,
inj: isInj(text),
text: cs[i]
}
arr.push(JSON.stringify(obj))
}
return arr
}
function requestConfig() {
return new Promise(function (resolve) {
var done = false
function onCfg(e) {
if (done) return
done = true
try {
cfg = JSON.parse(e.detail)
} catch (err) {
cfg = null
}
window.removeEventListener('dshmem:log-config', onCfg)
resolve(cfg)
}
window.addEventListener('dshmem:log-config', onCfg)
try {
window.dispatchEvent(new CustomEvent('dshmem:log-request'))
} catch (err) {
done = true
resolve(null)
}
setTimeout(function () {
if (done) return
done = true
window.removeEventListener('dshmem:log-config', onCfg)
resolve(null)
}, 2000)
})
}
function sendLines(file, lines) {
if (!cfg || !cfg.token) return Promise.resolve(false)
var headers = { 'Content-Type': 'application/json' }
headers.Authorization = 'Bearer ' + cfg.token
return fetch(BASE + '/log/append', {
method: 'POST',
headers: headers,
body: JSON.stringify({ file: file, lines: lines })
}).then(function (r) { return r.ok }).catch(function () { return false })
}
function extractAssistant(sse) {
if (!sse) return ''
var out = []
var blocks = sse.split(SEP)
for (var i = 0; i !== blocks.length; i++) {
var block = blocks[i]
var idx = block.indexOf('data: ')
if (idx === -1) continue
var json = block.slice(idx + 6).trim()
if (!json) continue
var o = null
try { o = JSON.parse(json) } catch (e) { continue }
if (!o || typeof o !== 'object') continue
if (o.p) continue
if (typeof o.v !== 'string') continue
if (Object.keys(o).length !== 1) continue
out.push(o.v)
}
return out.join('')
}
function process(entry) {
if (!entry || !entry.url) return
if (entry.url.indexOf('chat/completion') === -1) return
if (!cfg) {
requestConfig().then(function () { process(entry) })
return
}
if (!cfg.enabled) return
var messages = []
try {
var req = JSON.parse(entry.body)
if (req && Array.isArray(req.messages)) messages = req.messages
} catch (e) { return }
var assistant = ''
try { assistant = extractAssistant(entry.resp || '') } catch (e) { }
var all = messages.slice()
if (assistant) all.push({ role: 'assistant', content: assistant })
var file = today() + '.jsonl'
var lines = []
for (var i = 0; i !== all.length; i++) {
var msg = all[i]
if (!msg || typeof msg.content !== 'string') continue
var text = msg.content
if (!text) continue
var role = msg.role || 'user'
var h = hash(role + ':' + text)
if (seen[h]) continue
seen[h] = true
var ls = linesFor(role + '-' + h, role, text, Date.now())
for (var j = 0; j !== ls.length; j++) lines.push(ls[j])
}
if (!lines.length) return
sendLines(file, lines).then(function (ok) {
if (ok) console.log('[chat-log] saved', lines.length)
})
}
var origOpen = XMLHttpRequest.prototype.open
var origSend = XMLHttpRequest.prototype.send
XMLHttpRequest.prototype.open = function (m, u) {
this.__chatUrl = String(u || '')
return origOpen.apply(this, arguments)
}
XMLHttpRequest.prototype.send = function (body) {
var self = this
var bodyStr = typeof body === 'string' ? body : ''
this.addEventListener('load', function () {
try {
process({ url: self.__chatUrl, body: bodyStr, resp: self.responseText || '' })
} catch (e) {
console.warn('[chat-log] error', String(e))
}
})
return origSend.apply(this, arguments)
}
requestConfig()
})()

15
manifest.json

@ -24,13 +24,24 @@ @@ -24,13 +24,24 @@
"content/selectors.js",
"content/parser.js",
"content/storage-bridge.js",
"content/bridge-client.js",
"content/bridge-client.js",
"content/panel.js",
"content/content.js"
"content/content.js",
"content/chat-logger.js"
],
"css": [
"content/overlay.css"
]
}
],
"web_accessible_resources": [
{
"resources": [
"injected/chat-logger.js"
],
"matches": [
"https://chat.deepseek.com/*"
]
}
]
}

2
memory/state.json

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
{
"version": 1,
"updatedAt": "2026-10-31T03:11:08.923Z",
"updatedAt": "2026-10-31T22:42:50.200Z",
"memories": {},
"skills": []
}
Loading…
Cancel
Save