@dev-anywhere/proxy 0.0.5 → 0.0.6

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/dist/serve.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  KnownContentBlockSchema,
6
6
  SeqCounter,
7
7
  StreamJsonEventSchema
8
- } from "./chunk-2Q3Z3ICU.js";
8
+ } from "./chunk-2XDZRLST.js";
9
9
  import {
10
10
  createFSM,
11
11
  defineFSM,
@@ -14,7 +14,7 @@ import {
14
14
  serviceLogger,
15
15
  shouldReleaseApprovalWait,
16
16
  stateAfterApprovalRelease
17
- } from "./chunk-UGFYGF3Y.js";
17
+ } from "./chunk-XRTTHTM2.js";
18
18
  import {
19
19
  spawnScript
20
20
  } from "./chunk-ZUWAB67J.js";
@@ -27,6 +27,8 @@ import {
27
27
  CONFIG_PATH,
28
28
  ControlErrorCode,
29
29
  DATA_DIR,
30
+ HOOK_REGISTRY_PATH,
31
+ MessageEnvelopeSchema,
30
32
  PID_PATH,
31
33
  SESSIONS_PATH,
32
34
  SOCK_PATH,
@@ -39,11 +41,11 @@ import {
39
41
  serializeWorkerMsg,
40
42
  sessionPaths,
41
43
  tildify
42
- } from "./chunk-QJ5CQDK7.js";
44
+ } from "./chunk-VHCL7NVJ.js";
43
45
 
44
46
  // src/serve.ts
45
47
  import { createServer as createServer2 } from "net";
46
- import { unlinkSync as unlinkSync3, writeFileSync as writeFileSync4, chmodSync, rmSync as rmSync2 } from "fs";
48
+ import { unlinkSync as unlinkSync3, writeFileSync as writeFileSync5, chmodSync, rmSync as rmSync2 } from "fs";
47
49
 
48
50
  // src/serve/session-manager.ts
49
51
  import { mkdirSync, readFileSync, renameSync, writeFileSync, existsSync } from "fs";
@@ -764,7 +766,7 @@ import { readdir as readdir2, mkdir } from "fs/promises";
764
766
  import { join as join4, isAbsolute as isAbsolute2, normalize } from "path";
765
767
 
766
768
  // src/serve/session-history.ts
767
- import { readdir, stat, access } from "fs/promises";
769
+ import { readdir, stat, access, open } from "fs/promises";
768
770
  import { createReadStream } from "fs";
769
771
  import { join as join2 } from "path";
770
772
  import { homedir as homedir2 } from "os";
@@ -864,49 +866,123 @@ async function scanCodexSessionHistory() {
864
866
  }
865
867
  return entries;
866
868
  }
867
- async function readSessionMessages(claudeSessionId) {
869
+ var DEFAULT_HISTORY_PAGE_LIMIT = 50;
870
+ var MAX_HISTORY_PAGE_LIMIT = 200;
871
+ var HISTORY_READ_CHUNK_BYTES = 64 * 1024;
872
+ var HISTORY_CURSOR_PREFIX = "b:";
873
+ function normalizeHistoryPageLimit(limit) {
874
+ if (typeof limit !== "number" || !Number.isFinite(limit)) return DEFAULT_HISTORY_PAGE_LIMIT;
875
+ return Math.max(1, Math.min(MAX_HISTORY_PAGE_LIMIT, Math.floor(limit)));
876
+ }
877
+ function encodeHistoryCursor(offset) {
878
+ return `${HISTORY_CURSOR_PREFIX}${Math.max(0, Math.floor(offset))}`;
879
+ }
880
+ function decodeHistoryCursor(cursor, fileSize) {
881
+ if (!cursor) return fileSize;
882
+ const raw = cursor.startsWith(HISTORY_CURSOR_PREFIX) ? cursor.slice(HISTORY_CURSOR_PREFIX.length) : cursor;
883
+ const parsed = Number(raw);
884
+ if (!Number.isInteger(parsed) || parsed < 0) return fileSize;
885
+ return Math.min(parsed, fileSize);
886
+ }
887
+ async function findClaudeSessionFile(claudeSessionId) {
868
888
  let projectDirs;
869
889
  try {
870
890
  projectDirs = await readdir(claudeProjectsDir());
871
891
  } catch {
872
- return [];
892
+ return null;
873
893
  }
874
894
  for (const encodedDir of projectDirs) {
875
895
  const filePath = join2(claudeProjectsDir(), encodedDir, `${claudeSessionId}.jsonl`);
876
896
  try {
877
897
  await access(filePath);
898
+ return filePath;
878
899
  } catch {
879
900
  continue;
880
901
  }
881
- const messages = [];
882
- return new Promise((resolve) => {
883
- const rl = createInterface({
884
- input: createReadStream(filePath, { encoding: "utf-8" }),
885
- crlfDelay: Infinity
886
- });
887
- rl.on("line", (line) => {
888
- if (!line.trim()) return;
902
+ }
903
+ return null;
904
+ }
905
+ function extractConversationMessageFromJson(obj) {
906
+ if (!obj || typeof obj !== "object") return null;
907
+ const record = obj;
908
+ if (record.type === "user") {
909
+ if (record.isMeta) return null;
910
+ const text = extractConversationText(record.message);
911
+ if (!text) return null;
912
+ const ts = typeof record.timestamp === "string" ? new Date(record.timestamp).getTime() : void 0;
913
+ return { role: "user", text, timestamp: ts };
914
+ }
915
+ if (record.type === "assistant") {
916
+ const text = extractConversationText(record.message);
917
+ if (!text) return null;
918
+ const ts = typeof record.timestamp === "string" ? new Date(record.timestamp).getTime() : void 0;
919
+ return { role: "assistant", text, timestamp: ts };
920
+ }
921
+ return null;
922
+ }
923
+ function splitLineSegments(block, blockStart) {
924
+ const segments = [];
925
+ let start = 0;
926
+ for (let i = 0; i < block.length; i += 1) {
927
+ if (block[i] !== 10) continue;
928
+ segments.push({ start: blockStart + start, line: block.subarray(start, i) });
929
+ start = i + 1;
930
+ }
931
+ segments.push({ start: blockStart + start, line: block.subarray(start) });
932
+ return segments;
933
+ }
934
+ function stripCarriageReturn(line) {
935
+ return line.length > 0 && line[line.length - 1] === 13 ? line.subarray(0, -1) : line;
936
+ }
937
+ async function readSessionMessagesPageFromFile(filePath, options = {}) {
938
+ const limit = normalizeHistoryPageLimit(options.limit);
939
+ const file = await open(filePath, "r");
940
+ try {
941
+ const fileStat = await file.stat();
942
+ const endOffset = decodeHistoryCursor(options.before, fileStat.size);
943
+ if (endOffset <= 0) return { messages: [], hasMore: false };
944
+ let position = endOffset;
945
+ let carry = Buffer.alloc(0);
946
+ const collected = [];
947
+ while (position > 0 && collected.length <= limit) {
948
+ const readSize = Math.min(HISTORY_READ_CHUNK_BYTES, position);
949
+ position -= readSize;
950
+ const chunk = Buffer.alloc(readSize);
951
+ await file.read(chunk, 0, readSize, position);
952
+ const block = carry.length > 0 ? Buffer.concat([chunk, carry]) : chunk;
953
+ const segments = splitLineSegments(block, position);
954
+ const firstCompleteIndex = position > 0 ? 1 : 0;
955
+ carry = position > 0 ? segments[0]?.line ?? Buffer.alloc(0) : Buffer.alloc(0);
956
+ for (let i = segments.length - 1; i >= firstCompleteIndex; i -= 1) {
957
+ const segment = segments[i];
958
+ if (!segment) continue;
959
+ const line = stripCarriageReturn(segment.line);
960
+ if (line.length === 0) continue;
889
961
  try {
890
- const obj = JSON.parse(line);
891
- if (obj.type === "user") {
892
- if (obj.isMeta) return;
893
- const text = extractConversationText(obj.message);
894
- if (!text) return;
895
- const ts = typeof obj.timestamp === "string" ? new Date(obj.timestamp).getTime() : void 0;
896
- messages.push({ role: "user", text, timestamp: ts });
897
- } else if (obj.type === "assistant") {
898
- const text = extractConversationText(obj.message);
899
- const ts = typeof obj.timestamp === "string" ? new Date(obj.timestamp).getTime() : void 0;
900
- if (text) messages.push({ role: "assistant", text, timestamp: ts });
901
- }
962
+ const parsed = JSON.parse(line.toString("utf-8"));
963
+ const message = extractConversationMessageFromJson(parsed);
964
+ if (!message) continue;
965
+ collected.push({ ...message, cursor: encodeHistoryCursor(segment.start) });
966
+ if (collected.length > limit) break;
902
967
  } catch {
903
968
  }
904
- });
905
- rl.on("close", () => resolve(messages));
906
- rl.on("error", () => resolve(messages));
907
- });
969
+ }
970
+ }
971
+ const page = collected.slice(0, limit).reverse();
972
+ const hasMore = collected.length > limit;
973
+ return {
974
+ messages: page,
975
+ hasMore,
976
+ ...hasMore && page[0]?.cursor ? { nextBefore: page[0].cursor } : {}
977
+ };
978
+ } finally {
979
+ await file.close();
908
980
  }
909
- return [];
981
+ }
982
+ async function readSessionMessagesPage(claudeSessionId, options = {}) {
983
+ const filePath = await findClaudeSessionFile(claudeSessionId);
984
+ if (!filePath) return { messages: [], hasMore: false };
985
+ return readSessionMessagesPageFromFile(filePath, options);
910
986
  }
911
987
  function collapseWhitespace(text) {
912
988
  return text.replace(/\s+/g, " ").trim();
@@ -2008,6 +2084,21 @@ var RelayInputHandlers = class {
2008
2084
  serviceLogger.warn({ sessionId }, "Remote input dropped: JSON worker socket not available");
2009
2085
  return;
2010
2086
  }
2087
+ const timestamp = typeof msg.timestamp === "number" && Number.isFinite(msg.timestamp) ? msg.timestamp : Date.now();
2088
+ const seq = typeof msg.seq === "number" && Number.isInteger(msg.seq) && msg.seq >= 0 ? msg.seq : 0;
2089
+ const version = typeof msg.version === "string" ? msg.version : "1";
2090
+ const messageId = typeof payload?.messageId === "string" && payload.messageId.length > 0 ? payload.messageId : `${sessionId}-user-${timestamp}`;
2091
+ this.deps.relayConnection.sendEnvelope(
2092
+ MessageEnvelopeSchema.parse({
2093
+ type: "user_input",
2094
+ sessionId,
2095
+ seq,
2096
+ timestamp,
2097
+ source: "proxy",
2098
+ version,
2099
+ payload: { text, messageId }
2100
+ })
2101
+ );
2011
2102
  serviceLogger.info({ sessionId }, "Remote input forwarded to JSON worker");
2012
2103
  return;
2013
2104
  }
@@ -2047,32 +2138,45 @@ var RelayHistoryHandlers = class {
2047
2138
  const sid = msg.sessionId;
2048
2139
  if (!sid) return;
2049
2140
  const requestId = msg.requestId;
2141
+ const before = msg.before;
2142
+ const limit = msg.limit;
2050
2143
  const session = this.deps.sessionManager.getSession(sid);
2051
2144
  if (session?.claudeSessionId) {
2052
- readSessionMessages(session.claudeSessionId).then((messages) => {
2145
+ readSessionMessagesPage(session.claudeSessionId, { before, limit }).then((page) => {
2053
2146
  this.deps.relaySend(
2054
2147
  JSON.stringify({
2055
2148
  type: "session_history_messages",
2056
2149
  requestId,
2057
2150
  sessionId: sid,
2058
- messages
2151
+ ...before !== void 0 ? { before } : {},
2152
+ messages: page.messages,
2153
+ hasMore: page.hasMore,
2154
+ ...page.nextBefore !== void 0 ? { nextBefore: page.nextBefore } : {}
2059
2155
  })
2060
2156
  );
2061
2157
  serviceLogger.info(
2062
- { sessionId: sid, messageCount: messages.length },
2063
- "History messages sent on request"
2158
+ {
2159
+ sessionId: sid,
2160
+ before,
2161
+ hasMore: page.hasMore,
2162
+ nextBefore: page.nextBefore,
2163
+ messageCount: page.messages.length
2164
+ },
2165
+ "History message page sent on request"
2064
2166
  );
2065
2167
  }).catch((err) => {
2066
2168
  serviceLogger.warn(
2067
2169
  { sessionId: sid, error: String(err) },
2068
- "Failed to read session history messages on request"
2170
+ "Failed to read session history page on request"
2069
2171
  );
2070
2172
  this.deps.relaySend(
2071
2173
  JSON.stringify({
2072
2174
  type: "session_history_messages",
2073
2175
  requestId,
2074
2176
  sessionId: sid,
2075
- messages: []
2177
+ ...before !== void 0 ? { before } : {},
2178
+ messages: [],
2179
+ hasMore: false
2076
2180
  })
2077
2181
  );
2078
2182
  });
@@ -2082,7 +2186,9 @@ var RelayHistoryHandlers = class {
2082
2186
  type: "session_history_messages",
2083
2187
  requestId,
2084
2188
  sessionId: sid,
2085
- messages: []
2189
+ ...before !== void 0 ? { before } : {},
2190
+ messages: [],
2191
+ hasMore: false
2086
2192
  })
2087
2193
  );
2088
2194
  }
@@ -2834,19 +2940,30 @@ var RelaySessionCreateHandler = class {
2834
2940
  }
2835
2941
  }
2836
2942
  pushHistoryMessages(sessionId, resumeSessionId) {
2837
- readSessionMessages(resumeSessionId).then((messages) => {
2838
- if (messages.length === 0) return;
2943
+ readSessionMessagesPage(resumeSessionId).then((page) => {
2944
+ if (page.messages.length === 0) return;
2839
2945
  this.deps.relaySend(
2840
- JSON.stringify({ type: "session_history_messages", sessionId, messages })
2946
+ JSON.stringify({
2947
+ type: "session_history_messages",
2948
+ sessionId,
2949
+ messages: page.messages,
2950
+ hasMore: page.hasMore,
2951
+ ...page.nextBefore !== void 0 ? { nextBefore: page.nextBefore } : {}
2952
+ })
2841
2953
  );
2842
2954
  serviceLogger.info(
2843
- { sessionId, resumeSessionId, messageCount: messages.length },
2844
- "History messages sent for resumed session"
2955
+ {
2956
+ sessionId,
2957
+ resumeSessionId,
2958
+ messageCount: page.messages.length,
2959
+ hasMore: page.hasMore
2960
+ },
2961
+ "History message page sent for resumed session"
2845
2962
  );
2846
2963
  }).catch((err) => {
2847
2964
  serviceLogger.warn(
2848
2965
  { sessionId, error: String(err) },
2849
- "Failed to read session history messages"
2966
+ "Failed to read session history page"
2850
2967
  );
2851
2968
  });
2852
2969
  }
@@ -2864,6 +2981,7 @@ var RelayRouter = class {
2864
2981
  this.inputHandlers = new RelayInputHandlers({
2865
2982
  sessionManager: deps.sessionManager,
2866
2983
  workerRegistry: deps.workerRegistry,
2984
+ relayConnection: deps.relayConnection,
2867
2985
  terminalSockets: deps.terminalSockets,
2868
2986
  hostedPtyRegistry: deps.hostedPtyRegistry,
2869
2987
  jsonObserver: deps.jsonObserver
@@ -3808,6 +3926,21 @@ function handleTerminalConnection(socket, deps) {
3808
3926
 
3809
3927
  // src/serve/hook-registry.ts
3810
3928
  import { createHash, randomBytes } from "crypto";
3929
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "fs";
3930
+ import { dirname as dirname4 } from "path";
3931
+ import { z } from "zod";
3932
+ var PersistedHookSessionBindingSchema = z.object({
3933
+ sessionId: z.string(),
3934
+ provider: z.enum(["claude", "codex"]),
3935
+ marker: z.string(),
3936
+ tokenHash: z.string(),
3937
+ createdAt: z.number(),
3938
+ expiresAt: z.number().optional()
3939
+ });
3940
+ var PersistedHookRegistrySchema = z.object({
3941
+ version: z.literal(1),
3942
+ bindings: z.array(PersistedHookSessionBindingSchema)
3943
+ });
3811
3944
  function hashToken(token) {
3812
3945
  return createHash("sha256").update(token).digest("hex");
3813
3946
  }
@@ -3816,6 +3949,11 @@ function randomSecret() {
3816
3949
  }
3817
3950
  var HookRegistry = class {
3818
3951
  bindingsBySession = /* @__PURE__ */ new Map();
3952
+ persistPath;
3953
+ constructor(options = {}) {
3954
+ this.persistPath = options.persistPath;
3955
+ this.load();
3956
+ }
3819
3957
  registerSession(sessionId, provider, options = {}) {
3820
3958
  const now = options.now ?? Date.now();
3821
3959
  const token = randomSecret();
@@ -3828,6 +3966,7 @@ var HookRegistry = class {
3828
3966
  createdAt: now,
3829
3967
  ...options.ttlMs ? { expiresAt: now + options.ttlMs } : {}
3830
3968
  });
3969
+ this.save();
3831
3970
  return { sessionId, provider, marker, token };
3832
3971
  }
3833
3972
  verify(options) {
@@ -3843,7 +3982,50 @@ var HookRegistry = class {
3843
3982
  return this.bindingsBySession.get(sessionId) ?? null;
3844
3983
  }
3845
3984
  unregisterSession(sessionId) {
3846
- this.bindingsBySession.delete(sessionId);
3985
+ if (this.bindingsBySession.delete(sessionId)) {
3986
+ this.save();
3987
+ }
3988
+ }
3989
+ load() {
3990
+ if (!this.persistPath || !existsSync6(this.persistPath)) return;
3991
+ try {
3992
+ const parsed = PersistedHookRegistrySchema.parse(
3993
+ JSON.parse(readFileSync6(this.persistPath, "utf8"))
3994
+ );
3995
+ this.bindingsBySession.clear();
3996
+ for (const binding of parsed.bindings) {
3997
+ this.bindingsBySession.set(binding.sessionId, binding);
3998
+ }
3999
+ } catch (err) {
4000
+ serviceLogger.warn(
4001
+ { path: this.persistPath, error: String(err) },
4002
+ "Failed to load hook registry state"
4003
+ );
4004
+ }
4005
+ }
4006
+ save() {
4007
+ if (!this.persistPath) return;
4008
+ try {
4009
+ mkdirSync4(dirname4(this.persistPath), { recursive: true });
4010
+ const tmpPath = `${this.persistPath}.${process.pid}.${Date.now()}.tmp`;
4011
+ writeFileSync4(
4012
+ tmpPath,
4013
+ JSON.stringify(
4014
+ {
4015
+ version: 1,
4016
+ bindings: Array.from(this.bindingsBySession.values())
4017
+ },
4018
+ null,
4019
+ 2
4020
+ )
4021
+ );
4022
+ renameSync2(tmpPath, this.persistPath);
4023
+ } catch (err) {
4024
+ serviceLogger.warn(
4025
+ { path: this.persistPath, error: String(err) },
4026
+ "Failed to persist hook registry state"
4027
+ );
4028
+ }
3847
4029
  }
3848
4030
  };
3849
4031
 
@@ -4038,7 +4220,7 @@ var HookServer = class {
4038
4220
 
4039
4221
  // src/serve/provider-hook-runtime.ts
4040
4222
  async function createProviderHookRuntime(options) {
4041
- const hookRegistry = new HookRegistry();
4223
+ const hookRegistry = new HookRegistry({ persistPath: HOOK_REGISTRY_PATH });
4042
4224
  const hookEventRouter = new HookEventRouter({
4043
4225
  relayConnection: options.relayConnection,
4044
4226
  agentStatusRegistry: options.agentStatusRegistry,
@@ -4331,7 +4513,7 @@ async function startService(options) {
4331
4513
  });
4332
4514
  });
4333
4515
  server.listen(SOCK_PATH, () => {
4334
- writeFileSync4(PID_PATH, String(process.pid));
4516
+ writeFileSync5(PID_PATH, String(process.pid));
4335
4517
  chmodSync(SOCK_PATH, 384);
4336
4518
  serviceLogger.info({ pid: process.pid, sock: SOCK_PATH }, "Service started");
4337
4519
  });