Sign In

@neeter/server

Package Overview
Dependencies
Maintainers
1
Versions
34
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@neeter/server - npm Package Compare versions

Comparing version
0.12.0
to
0.13.0
+53
-2
dist/router.js

@@ -38,7 +38,58 @@ import { Hono } from "hono";

}
const sdkId = body.sdkSessionId.trim();
const checkpointId = body.resumeSessionAt?.trim() || undefined;
// The SDK's resumeSessionAt uses "includes" semantics — the agent's
// context retains the target message. Our UI and file rewind use
// "before" semantics. For rewind (not fork), translate by passing the
// previous checkpoint's UUID so the agent context ends before the
// target message. Also truncate the persisted event log.
let sdkResumeAt = checkpointId;
if (checkpointId && !body.forkSession) {
const store = sessions.getStore();
if (store) {
const record = await store.load(sdkId);
if (record) {
const allCheckpoints = record.events
.filter((e) => e.event === "checkpoint")
.map((e) => JSON.parse(e.data).userMessageUuid);
const targetIdx = allCheckpoints.indexOf(checkpointId);
if (targetIdx > 0) {
sdkResumeAt = allCheckpoints[targetIdx - 1];
}
else if (targetIdx === 0) {
sdkResumeAt = undefined;
}
const cpIdx = record.events.findIndex((e) => e.event === "checkpoint" && JSON.parse(e.data).userMessageUuid === checkpointId);
if (cpIdx >= 0) {
let cutIdx = cpIdx;
for (let i = cpIdx - 1; i >= 0; i--) {
if (record.events[i].event === "user_message") {
cutIdx = i;
break;
}
}
const truncated = record.events.slice(0, cutIdx);
await store.delete(sdkId);
await store.save(sdkId, { meta: record.meta, events: truncated });
}
}
}
}
const session = sessions.resume({
sdkSessionId: body.sdkSessionId.trim(),
sdkSessionId: sdkId,
forkSession: body.forkSession,
resumeSessionAt: body.resumeSessionAt?.trim() || undefined,
resumeSessionAt: sdkResumeAt,
});
// Wait for the SDK subprocess to initialize, then restore file
// checkpoints before returning. resumeSessionAt only truncates
// conversation context — files must be rewound explicitly.
await session.messageIterator.initializationResult();
if (checkpointId) {
try {
await session.messageIterator.rewindFiles(checkpointId);
}
catch (err) {
console.warn("[resume] rewindFiles failed:", err.message);
}
}
return c.json({ sessionId: session.id });

@@ -45,0 +96,0 @@ });

@@ -52,2 +52,3 @@ import { type HookCallbackMatcher, type HookEvent, type McpServerConfig, type Query } from "@anthropic-ai/claude-agent-sdk";

cwd?: string;
enableFileCheckpointing?: boolean;
context: TCtx;

@@ -60,2 +61,4 @@ pushMessage(text: string): void;

lastActivityAt: number;
/** When true, streamSession skips replay events from the SDK subprocess. Cleared on pushMessage. */
replayGate?: boolean;
}

@@ -62,0 +65,0 @@ export declare function sessionMeta(session: Session<unknown>): SessionHistoryEntry;

@@ -179,4 +179,7 @@ import { query, } from "@anthropic-ai/claude-agent-sdk";

cwd: init.cwd,
enableFileCheckpointing: !!init.enableFileCheckpointing,
context: init.context,
replayGate: !!extraQueryOptions?.resume,
pushMessage: (text) => {
session.replayGate = false;
session.lastActivityAt = Date.now();

@@ -183,0 +186,0 @@ if (!session.firstPrompt)

@@ -136,4 +136,8 @@ import { PushChannel } from "./push-channel.js";

case "user": {
const msg = message.message;
// Only emit checkpoints for real user messages (string content),
// not tool-result round-trips (array content with tool_result blocks).
const isToolResult = Array.isArray(msg?.content);
const uuid = message.uuid;
if (uuid) {
if (uuid && !isToolResult) {
events.push({

@@ -144,3 +148,2 @@ event: "checkpoint",

}
const msg = message.message;
if (Array.isArray(msg?.content)) {

@@ -185,2 +188,3 @@ for (const block of msg.content) {

mcpServers: message.mcp_servers ?? [],
...(session.enableFileCheckpointing ? { fileCheckpointing: true } : {}),
};

@@ -242,2 +246,7 @@ events.push({

for (const evt of events) {
// When resuming with resumeSessionAt, the SDK subprocess replays
// historical messages. Skip them — only session_init is needed
// so the client can set sdkSessionId and mcpServers.
if (session.replayGate && evt.event !== "session_init")
continue;
output.push(evt);

@@ -244,0 +253,0 @@ }

+2
-2
{
"name": "@neeter/server",
"version": "0.12.0",
"version": "0.13.0",
"description": "Hono server toolkit for building chat UIs on top of the Claude Agent SDK",

@@ -24,3 +24,3 @@ "license": "MIT",

"dependencies": {
"@neeter/types": "0.12.0"
"@neeter/types": "0.13.0"
},

@@ -27,0 +27,0 @@ "peerDependencies": {

@@ -53,3 +53,3 @@ # @neeter/server

| [basic-chat](https://github.com/quantumleeps/neeter/tree/main/examples/basic-chat) | Minimal server + client setup |
| [live-preview](https://github.com/quantumleeps/neeter/tree/main/examples/live-preview) | Per-session sandboxes, custom events, abort |
| [code-workbench](https://github.com/quantumleeps/neeter/tree/main/examples/code-workbench) | Per-session sandboxes, persistence, file checkpointing, custom events |

@@ -56,0 +56,0 @@ ## Documentation