UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

170 lines (169 loc) 6.25 kB
import { f as asSafeIntegerInRange } from "./number-coercion-CLj0HTDM.js"; import { c as isRecord } from "./record-coerce-DItp3I4t.js"; import "./string-coerce-runtime-GQa0ehRA.js"; import { a as classifyClaudeCliHistoryLine, c as isExternalUserText, o as classifyClaudeCliHistoryMessage } from "./session-catalog-C5rciJKU.js"; import fs from "node:fs/promises"; //#region extensions/anthropic/session-upstream-activity.ts const MAX_CLAUDE_UPSTREAM_SCAN_BYTES = 1048576; async function readFileRange(handle, position, length) { const buffer = Buffer.alloc(length); let offset = 0; while (offset < length) { const { bytesRead } = await handle.read(buffer, offset, length - offset, position + offset); if (bytesRead <= 0) break; offset += bytesRead; } return offset === length ? buffer : buffer.subarray(0, offset); } async function link$1(sessionKey, hostId, threadId, listSessions) { if (hostId !== "gateway:local") return { sessionKey }; try { const record = (await listSessions()).find((candidate) => candidate.threadId === threadId); const stat = record ? await fs.stat(record.filePath).catch(() => void 0) : void 0; return record && stat?.isFile() ? { sessionKey, upstream: { kind: "claude-cli", ref: { filePath: record.filePath }, marker: { offset: stat.size } } } : { sessionKey }; } catch { return { sessionKey }; } } function linkRemote(sessionKey, nodeId, threadId, markerUuid) { return { sessionKey, upstream: { kind: "claude-cli", ref: { nodeId, threadId }, marker: { uuid: markerUuid } } }; } async function linkContinued(params) { if (params.hostId === "gateway:local") return await link$1(params.sessionKey, params.hostId, params.threadId, params.listLocalSessions); if (!params.hostId.startsWith("node:")) return { sessionKey: params.sessionKey }; try { const newest = (params.history ?? await params.readRemote())[0]; if (newest && !newest.uuid) return { sessionKey: params.sessionKey }; return linkRemote(params.sessionKey, params.hostId.slice(5), params.threadId, newest?.uuid ?? null); } catch { return { sessionKey: params.sessionKey }; } } function readFilePath(probe) { return isRecord(probe.upstreamRef) && typeof probe.upstreamRef.filePath === "string" ? probe.upstreamRef.filePath : void 0; } function readMarkerOffset(probe) { if (!isRecord(probe.marker)) return; const offset = probe.marker.offset ?? probe.marker.size; return asSafeIntegerInRange(offset, { min: 0 }); } async function checkClaudeSessionUpstreamActivity(probe) { if (probe.upstreamKind !== "claude-cli") return; const filePath = readFilePath(probe); const markerOffset = readMarkerOffset(probe); if (!filePath || markerOffset === void 0) return; let handle; try { handle = await fs.open(filePath, "r"); } catch (error) { return isRecord(error) && error.code === "ENOENT" ? { kind: "missing", sessionKey: probe.sessionKey } : void 0; } try { const stat = await handle.stat(); if (!stat.isFile()) return { kind: "missing", sessionKey: probe.sessionKey }; if (stat.size <= markerOffset) return; const readLength = Math.min(stat.size - markerOffset, MAX_CLAUDE_UPSTREAM_SCAN_BYTES); const tail = await readFileRange(handle, markerOffset, readLength); const lastNewline = tail.lastIndexOf(10); if (lastNewline < 0) return; const completeTail = tail.subarray(0, lastNewline + 1); let humanTurns = 0; let occurredAt; for (const [lineIndex, line] of completeTail.toString("utf8").split(/\r?\n/).entries()) { if (!line.trim()) continue; const classification = classifyClaudeCliHistoryLine({ line, cliSessionId: probe.threadId, sourceLineNumber: lineIndex + 1 }); if (!classification.humanTurn || !isExternalUserText(probe, classification.userText)) continue; humanTurns += 1; occurredAt = Math.max(occurredAt ?? 0, classification.occurredAt ?? stat.mtimeMs); } const nextOffset = markerOffset + lastNewline + 1; return { kind: "activity", sessionKey: probe.sessionKey, humanTurns, nextMarker: { offset: nextOffset }, ...humanTurns > 0 ? { occurredAt: occurredAt ?? stat.mtimeMs, dedupeId: String(nextOffset) } : {} }; } finally { await handle.close(); } } function readMarkerUuid(probe) { if (!isRecord(probe.marker)) return; return probe.marker.uuid === null || typeof probe.marker.uuid === "string" ? probe.marker.uuid : void 0; } async function checkRemoteClaudeSessionUpstreamActivity(probe, readRemote) { if (!isRecord(probe.upstreamRef) || typeof probe.upstreamRef.nodeId !== "string" || probe.hostId !== `node:${probe.upstreamRef.nodeId}`) return; const markerUuid = readMarkerUuid(probe); if (markerUuid === void 0) return; const items = await readRemote(probe); const markerIndex = markerUuid === null ? -1 : items.findIndex((item) => item.uuid === markerUuid); const newItems = markerIndex < 0 ? items : items.slice(0, markerIndex); const newest = newItems[0]; if (!newest?.uuid) return; let humanTurns = 0; let occurredAt; for (const [itemIndex, item] of newItems.entries()) { if (item.type !== "userMessage") continue; const classification = classifyClaudeCliHistoryMessage({ content: item.content ?? item.text, timestamp: item.timestamp, cliSessionId: probe.threadId, sourceLineNumber: itemIndex + 1 }); if (!classification.humanTurn || !isExternalUserText(probe, classification.userText)) continue; humanTurns += 1; occurredAt = Math.max(occurredAt ?? 0, classification.occurredAt ?? Date.now()); } const activityId = newest.uuid; return { kind: "activity", sessionKey: probe.sessionKey, humanTurns, nextMarker: { uuid: activityId }, ...humanTurns > 0 ? { occurredAt: occurredAt ?? Date.now(), dedupeId: activityId } : {} }; } async function checkClaudeUpstreamActivity(probes, readRemote) { const activities = []; for (const probe of probes) try { const activity = readFilePath(probe) ? await checkClaudeSessionUpstreamActivity(probe) : readRemote ? await checkRemoteClaudeSessionUpstreamActivity(probe, readRemote) : void 0; if (activity) activities.push(activity); } catch {} return activities; } //#endregion export { linkContinued as n, checkClaudeUpstreamActivity as t };