chore: 保存当前架构收口与 bug 修复快照
归档本轮 P0/P1 bug 修复、设计审查迁移、AI selection scope 收口与 stream contract 调整,并保留当前 05 主线迁移起点。
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
import { createInterface } from 'node:readline';
|
||||
import { stdin, stdout } from 'node:process';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { AsyncLocalStorage } from 'node:async_hooks';
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { homedir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
@@ -33,6 +34,97 @@ function debugLog(message) {
|
||||
if (DEBUG) process.stderr.write(`${message}\n`);
|
||||
}
|
||||
|
||||
const toolContextStorage = new AsyncLocalStorage();
|
||||
|
||||
function isWriteMnoteTool(toolName) {
|
||||
return !['mnote.doc.fetch', 'mnote.page.get', 'mnote.block.fetch'].includes(toolName);
|
||||
}
|
||||
|
||||
function stableIdPart(value, fallback) {
|
||||
const text = String(value || fallback || '')
|
||||
.trim()
|
||||
.replace(/[^a-zA-Z0-9_.:-]+/g, '_')
|
||||
.slice(0, 80);
|
||||
return text || String(fallback || 'unknown');
|
||||
}
|
||||
|
||||
function buildMnoteToolPayload(toolName, rawArgs = {}, context = {}) {
|
||||
const {
|
||||
actorId,
|
||||
sessionId,
|
||||
runId,
|
||||
toolCallId,
|
||||
traceId,
|
||||
idempotencyKey,
|
||||
dryRun,
|
||||
workspaceId,
|
||||
...args
|
||||
} = rawArgs || {};
|
||||
const effectiveSessionId =
|
||||
sessionId || context.sessionId || context.acpSessionId || `reasonix_acp_session_${randomUUID()}`;
|
||||
const effectiveRunId = runId || context.runId || effectiveSessionId;
|
||||
const effectiveToolCallId =
|
||||
toolCallId || `reasonix_${stableIdPart(toolName, 'tool')}_${randomUUID()}`;
|
||||
const effectiveTraceId = traceId || context.traceId || `trace_${stableIdPart(effectiveRunId, 'run')}`;
|
||||
const writeTool = isWriteMnoteTool(toolName);
|
||||
const effectiveDryRun = typeof dryRun === 'boolean' ? dryRun : writeTool ? false : false;
|
||||
const effectiveIdempotencyKey =
|
||||
idempotencyKey ||
|
||||
`idem_${stableIdPart(toolName, 'tool')}_${stableIdPart(effectiveRunId, 'run')}_${stableIdPart(effectiveToolCallId, 'call')}`;
|
||||
return {
|
||||
toolName,
|
||||
args,
|
||||
workspaceId: workspaceId || context.workspaceId || 'default',
|
||||
documentId: args.documentId || context.documentId,
|
||||
actorId: actorId || context.actorId || process.env.MNOTE_ACTOR_ID || 'reasonix-acp',
|
||||
sessionId: effectiveSessionId,
|
||||
runId: effectiveRunId,
|
||||
toolCallId: effectiveToolCallId,
|
||||
traceId: effectiveTraceId,
|
||||
dryRun: effectiveDryRun,
|
||||
idempotencyKey: effectiveIdempotencyKey,
|
||||
};
|
||||
}
|
||||
|
||||
if (process.env.MNOTE_REASONIX_ACP_SELFTEST === '1') {
|
||||
const payload = buildMnoteToolPayload(
|
||||
'mnote.doc.markdown_edit',
|
||||
{
|
||||
workspaceId: 'ws_demo',
|
||||
documentId: 'doc_1',
|
||||
operations: [{ search: '旧', replace: '新' }],
|
||||
},
|
||||
{
|
||||
actorId: 'user_1',
|
||||
sessionId: 'sess_1',
|
||||
runId: 'run_1',
|
||||
traceId: 'trace_1',
|
||||
},
|
||||
);
|
||||
const required = [
|
||||
'toolName',
|
||||
'workspaceId',
|
||||
'documentId',
|
||||
'actorId',
|
||||
'sessionId',
|
||||
'runId',
|
||||
'toolCallId',
|
||||
'traceId',
|
||||
'dryRun',
|
||||
'idempotencyKey',
|
||||
];
|
||||
for (const key of required) {
|
||||
if (payload[key] === undefined || payload[key] === null || payload[key] === '') {
|
||||
throw new Error(`selftest missing ${key}`);
|
||||
}
|
||||
}
|
||||
if (payload.args.workspaceId !== undefined || payload.args.actorId !== undefined) {
|
||||
throw new Error('selftest expected identity fields outside args');
|
||||
}
|
||||
process.stderr.write('[reasonix-acp-mnote] selftest ok\n');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// 读取 DeepSeek API key:先环境变量,再 Reasonix 官方 JSON 配置,最后兼容旧 YAML。
|
||||
function loadApiKey() {
|
||||
if (process.env.DEEPSEEK_API_KEY) return process.env.DEEPSEEK_API_KEY;
|
||||
@@ -229,14 +321,15 @@ const REASONIX_TOOL_TO_MNOTE_TOOL = {
|
||||
|
||||
async function callMnoteTool(toolName, args) {
|
||||
const url = `${MNOTE_WEB_URL}/api/hermes/tools/mnote/call`;
|
||||
const payload = buildMnoteToolPayload(toolName, args, toolContextStorage.getStore() || {});
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
toolName,
|
||||
args,
|
||||
workspaceId: args.workspaceId || 'default',
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-mnote-actor-id': payload.actorId,
|
||||
'x-mnote-workspace-id': payload.workspaceId,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text().catch(() => '');
|
||||
@@ -357,6 +450,15 @@ onRequest('session/prompt', async (params) => {
|
||||
}
|
||||
|
||||
session.aborter = new AbortController();
|
||||
const toolContext = {
|
||||
acpSessionId: params.sessionId,
|
||||
sessionId: params.mnoteSessionId || params.sessionId,
|
||||
runId: params.runId || params.mnoteRunId || params.sessionId,
|
||||
actorId: params.actorId || params.mnoteActorId,
|
||||
traceId: params.traceId || params.mnoteTraceId,
|
||||
workspaceId: params.workspaceId,
|
||||
documentId: params.documentId,
|
||||
};
|
||||
let stopReason = 'end_turn';
|
||||
let hasAssistantOutput = false;
|
||||
let hasToolCall = false;
|
||||
@@ -386,7 +488,8 @@ onRequest('session/prompt', async (params) => {
|
||||
}
|
||||
|
||||
try {
|
||||
for await (const ev of session.loop.step(text)) {
|
||||
await toolContextStorage.run(toolContext, async () => {
|
||||
for await (const ev of session.loop.step(text)) {
|
||||
if (session.aborter?.signal.aborted) {
|
||||
stopReason = 'cancelled';
|
||||
break;
|
||||
@@ -480,7 +583,8 @@ onRequest('session/prompt', async (params) => {
|
||||
if (ev.stats) {
|
||||
emitUsage(session.id, ev.stats.inputTokens || 0, ev.stats.cacheHitTokens || 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
const message = err.message || String(err);
|
||||
const stack = err.stack || '';
|
||||
|
||||
Reference in New Issue
Block a user