@love-moon/ai-sdk 0.13.0 → 0.13.2

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.
@@ -4,10 +4,15 @@ import { appendContextFilesToPrompt } from "../context-files.js";
4
4
  import { PROVIDER_MEDIA_CAPABILITIES, buildOpencodeParts } from "../media-adapters.js";
5
5
  import { assertMediaCapabilities, defaultPromptForMedia, resolveTurnMedia, } from "../media-input.js";
6
6
  import { OpencodeServerTransport } from "../transports/opencode-server-transport.js";
7
- import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
7
+ import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, noteToolFinished, noteToolStarted, proxyToEnv, sanitizeForLog, withActiveTool, } from "../shared.js";
8
8
  const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
9
9
  const MIN_TURN_DEADLINE_MS = 30 * 1000;
10
10
  const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
11
+ const SUMMARY_EVENT_GRACE_MS = 2000;
12
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
13
+ function isHeadersTimeoutError(error) {
14
+ return [error?.code, error?.cause?.code].includes("UND_ERR_HEADERS_TIMEOUT");
15
+ }
11
16
  function waitForever() {
12
17
  return new Promise(() => { });
13
18
  }
@@ -226,7 +231,7 @@ export class OpencodeSdkSession extends EventEmitter {
226
231
  resumeReady: Boolean(this.sessionId),
227
232
  manualResume: null,
228
233
  currentTurnStatus: this.getCurrentTurnStatus(),
229
- capabilities: { media: PROVIDER_MEDIA_CAPABILITIES[OPENCODE_PROVIDER_VARIANT] },
234
+ capabilities: { compact: true, media: PROVIDER_MEDIA_CAPABILITIES[OPENCODE_PROVIDER_VARIANT] },
230
235
  pid: this.transport.pid || undefined,
231
236
  };
232
237
  }
@@ -234,7 +239,7 @@ export class OpencodeSdkSession extends EventEmitter {
234
239
  return this.sessionInfo ? { ...this.sessionInfo } : null;
235
240
  }
236
241
  getCurrentTurnStatus() {
237
- return this.currentTurnStatus ? { ...this.currentTurnStatus } : null;
242
+ return withActiveTool(this.currentTurnStatus, this.currentTurn);
238
243
  }
239
244
  async ensureSessionInfo() {
240
245
  await this.boot();
@@ -729,7 +734,9 @@ export class OpencodeSdkSession extends EventEmitter {
729
734
  phase: "message_aggregation",
730
735
  reply_in_progress: true,
731
736
  status_line: statusLineForPhase("message_aggregation"),
732
- reply_preview: sanitizeSummary(this.collectAssistantText(messageState), 120),
737
+ reply_preview: currentTurn.suppressReply || messageState.info?.summary === true
738
+ ? undefined
739
+ : sanitizeSummary(this.collectAssistantText(messageState), 120),
733
740
  }, onProgress);
734
741
  return;
735
742
  }
@@ -742,6 +749,12 @@ export class OpencodeSdkSession extends EventEmitter {
742
749
  return;
743
750
  }
744
751
  if (part.type === "tool") {
752
+ if (part.state?.status === "completed" || part.state?.status === "error") {
753
+ noteToolFinished(currentTurn, part.callID || part.id);
754
+ }
755
+ else {
756
+ noteToolStarted(currentTurn, part.callID || part.id, part.tool, part.state?.input);
757
+ }
745
758
  const phase = toolPhaseForName(part.tool);
746
759
  const toolStatus = part.state?.status === "completed"
747
760
  ? part.state?.title || part.state?.output || ""
@@ -791,7 +804,9 @@ export class OpencodeSdkSession extends EventEmitter {
791
804
  phase: "message_aggregation",
792
805
  reply_in_progress: true,
793
806
  status_line: statusLineForPhase("message_aggregation"),
794
- reply_preview: sanitizeSummary(this.collectAssistantText(messageState), 120),
807
+ reply_preview: currentTurn.suppressReply || messageState.info?.summary === true
808
+ ? undefined
809
+ : sanitizeSummary(this.collectAssistantText(messageState), 120),
795
810
  }, onProgress);
796
811
  return;
797
812
  }
@@ -832,7 +847,8 @@ export class OpencodeSdkSession extends EventEmitter {
832
847
  if (currentTurn.activeAssistantMessageId === targetId) {
833
848
  currentTurn.activeAssistantMessageId = "";
834
849
  }
835
- if (!text) {
850
+ // Compaction summaries (manual or automatic) are context, not a reply.
851
+ if (!text || currentTurn.suppressReply || messageState.info?.summary === true) {
836
852
  return false;
837
853
  }
838
854
  currentTurn.fullText += text;
@@ -1140,6 +1156,160 @@ export class OpencodeSdkSession extends EventEmitter {
1140
1156
  }
1141
1157
  return true;
1142
1158
  }
1159
+ createTurnState(abortController, onProgress) {
1160
+ const currentTurn = {
1161
+ abortController,
1162
+ assistantMessages: new Map(),
1163
+ assistantMessageOrder: [],
1164
+ activeAssistantMessageId: "",
1165
+ messageRoles: new Map(),
1166
+ pendingMessageEvents: new Map(),
1167
+ fullText: "",
1168
+ items: [],
1169
+ lastAssistantInfo: null,
1170
+ onProgress,
1171
+ resolve: null,
1172
+ reject: null,
1173
+ settled: false,
1174
+ terminalWorkingStatusEmitted: false,
1175
+ };
1176
+ const completionPromise = new Promise((resolve, reject) => {
1177
+ currentTurn.resolve = resolve;
1178
+ currentTurn.reject = reject;
1179
+ });
1180
+ return { currentTurn, completionPromise };
1181
+ }
1182
+ /**
1183
+ * Model to summarize with: `session.summarize` requires one explicitly.
1184
+ * Prefer what the session actually ran on (last turn, then its recent
1185
+ * messages); the configured model is only a fallback.
1186
+ */
1187
+ async resolveCompactionModel() {
1188
+ const fromInfo = (info) => {
1189
+ const providerID = info?.providerID || info?.model?.providerID;
1190
+ const modelID = info?.modelID || info?.model?.modelID;
1191
+ return typeof providerID === "string" && providerID && typeof modelID === "string" && modelID
1192
+ ? { providerID, modelID }
1193
+ : null;
1194
+ };
1195
+ const lastModel = fromInfo(this.lastAssistantInfo);
1196
+ if (lastModel) {
1197
+ return lastModel;
1198
+ }
1199
+ if (typeof this.client?.session?.messages === "function") {
1200
+ const messages = await this.requestOrThrow(this.client.session.messages({ sessionID: this.sessionId, limit: 20 }, { throwOnError: true, responseStyle: "data" })).catch((error) => {
1201
+ this.trace(`compaction model lookup failed: ${extractErrorMessage(error)}`);
1202
+ return [];
1203
+ });
1204
+ for (const entry of [...(Array.isArray(messages) ? messages : [])].reverse()) {
1205
+ const model = fromInfo(entry?.info);
1206
+ if (model) {
1207
+ return model;
1208
+ }
1209
+ }
1210
+ }
1211
+ const configured = typeof this.options.model === "string" ? this.options.model.trim() : "";
1212
+ const slash = configured.indexOf("/");
1213
+ if (slash > 0 && slash < configured.length - 1) {
1214
+ return { providerID: configured.slice(0, slash), modelID: configured.slice(slash + 1) };
1215
+ }
1216
+ throw createTurnError("Opencode compaction needs a model, but none could be resolved for this session", {
1217
+ reason: "compact_failed",
1218
+ });
1219
+ }
1220
+ /**
1221
+ * Manually compact via the server's `session.summarize` (what the TUI's
1222
+ * `/compact` calls). It takes no focus instructions and answers `true`
1223
+ * even when summarizing fails, so success is judged from the summary
1224
+ * message itself.
1225
+ *
1226
+ * @param {import("../shared.js").CompactRequest} [request]
1227
+ * @param {{ onProgress?: Function }} [options]
1228
+ * @returns {Promise<import("../shared.js").CompactResult>}
1229
+ */
1230
+ async runCompact(request = {}, { onProgress = null } = {}) {
1231
+ if (this.closeRequested) {
1232
+ throw this.createSessionClosedError();
1233
+ }
1234
+ if (this.currentTurn) {
1235
+ throw createTurnError("Opencode turn already running", {
1236
+ reason: "turn_already_running",
1237
+ });
1238
+ }
1239
+ if (this.pendingHistorySeed || (!this.resumeSessionId && this.history.length === 0)) {
1240
+ return { compact: { status: "noop", instructionsApplied: false }, usage: null, metadata: {} };
1241
+ }
1242
+ this.markTurnStartedStatus();
1243
+ let model;
1244
+ try {
1245
+ await this.boot();
1246
+ if (typeof this.client?.session?.summarize !== "function") {
1247
+ throw new Error("Opencode summarize API is unavailable");
1248
+ }
1249
+ model = await this.resolveCompactionModel();
1250
+ }
1251
+ catch (error) {
1252
+ await this.failPendingTurnStart(error, onProgress);
1253
+ throw error;
1254
+ }
1255
+ const abortController = new AbortController();
1256
+ const { currentTurn, completionPromise } = this.createTurnState(abortController, onProgress);
1257
+ currentTurn.suppressReply = true;
1258
+ this.currentTurn = currentTurn;
1259
+ const closeGuard = this.createCloseGuard(() => {
1260
+ abortController.abort();
1261
+ void this.interruptCurrentTurn();
1262
+ });
1263
+ const turnTimeoutGuard = this.createTurnTimeoutGuard(() => {
1264
+ abortController.abort();
1265
+ void this.interruptCurrentTurn();
1266
+ });
1267
+ try {
1268
+ await this.emitWorkingStatus({
1269
+ phase: "context_compaction",
1270
+ reply_in_progress: true,
1271
+ status_line: statusLineForPhase("context_compaction"),
1272
+ }, onProgress);
1273
+ // The response only arrives once summarizing finishes, which can outlive
1274
+ // fetch's header timeout; `session.idle` marks completion either way.
1275
+ const summarize = this.requestOrThrow(this.client.session.summarize({ sessionID: this.sessionId, ...model, auto: false }, { throwOnError: true, responseStyle: "data", signal: abortController.signal })).catch((error) => (isHeadersTimeoutError(error) ? completionPromise : Promise.reject(error)));
1276
+ await Promise.race([Promise.race([summarize, completionPromise]), closeGuard.promise, turnTimeoutGuard.promise]);
1277
+ // Events trail the HTTP response on a separate stream; let idle land.
1278
+ await Promise.race([completionPromise, delay(SUMMARY_EVENT_GRACE_MS)]);
1279
+ const summaryInfo = currentTurn.lastAssistantInfo?.summary === true ? currentTurn.lastAssistantInfo : null;
1280
+ if (summaryInfo?.error || summaryInfo?.finish === "error") {
1281
+ throw createTurnError(`Opencode compaction failed: ${extractErrorMessage(summaryInfo.error?.data || summaryInfo.error) || "unknown error"}`, { reason: "compact_failed" });
1282
+ }
1283
+ await this.emitTerminalWorkingStatus(currentTurn, { phase: "turn_completed", status_done_line: "opencode compacted context" }, onProgress);
1284
+ return {
1285
+ // An empty session never gets here, so a summary whose events are
1286
+ // still in flight counts as compacted.
1287
+ compact: { status: "compacted", instructionsApplied: false },
1288
+ usage: this.buildUsageFromAssistantInfo(summaryInfo),
1289
+ metadata: { source: OPENCODE_PROVIDER_VARIANT, sessionId: this.sessionId || undefined, ...model },
1290
+ };
1291
+ }
1292
+ catch (error) {
1293
+ if (error?.reason === "turn_timeout") {
1294
+ await this.interruptCurrentTurn();
1295
+ }
1296
+ if (!this.closeRequested && error?.reason !== "session_closed") {
1297
+ await this.emitTerminalWorkingStatus(currentTurn, { phase: "turn_failed", status_done_line: extractErrorMessage(error) }, onProgress);
1298
+ }
1299
+ if (this.closeRequested && error?.reason !== "session_closed") {
1300
+ throw this.createSessionClosedError();
1301
+ }
1302
+ throw error;
1303
+ }
1304
+ finally {
1305
+ this.activeReplyTarget = "";
1306
+ if (this.currentTurn === currentTurn) {
1307
+ this.currentTurn = null;
1308
+ }
1309
+ closeGuard.cleanup();
1310
+ turnTimeoutGuard.cleanup();
1311
+ }
1312
+ }
1143
1313
  async runTurn(promptText, { useInitialImages = false, media: mediaInput, contextFiles, onProgress = null, jsonSchema = null } = {}) {
1144
1314
  if (this.closeRequested) {
1145
1315
  throw this.createSessionClosedError();
@@ -1169,26 +1339,7 @@ export class OpencodeSdkSession extends EventEmitter {
1169
1339
  }
1170
1340
  this.history.push({ role: "user", content: promptText });
1171
1341
  const abortController = new AbortController();
1172
- const currentTurn = {
1173
- abortController,
1174
- assistantMessages: new Map(),
1175
- assistantMessageOrder: [],
1176
- activeAssistantMessageId: "",
1177
- messageRoles: new Map(),
1178
- pendingMessageEvents: new Map(),
1179
- fullText: "",
1180
- items: [],
1181
- lastAssistantInfo: null,
1182
- onProgress,
1183
- resolve: null,
1184
- reject: null,
1185
- settled: false,
1186
- terminalWorkingStatusEmitted: false,
1187
- };
1188
- const completionPromise = new Promise((resolve, reject) => {
1189
- currentTurn.resolve = resolve;
1190
- currentTurn.reject = reject;
1191
- });
1342
+ const { currentTurn, completionPromise } = this.createTurnState(abortController, onProgress);
1192
1343
  this.currentTurn = currentTurn;
1193
1344
  const closeGuard = this.createCloseGuard(() => {
1194
1345
  abortController.abort();
package/dist/shared.d.ts CHANGED
@@ -20,6 +20,18 @@ export function normalizeLogger(logger: any): any;
20
20
  export function emitLog(logger: any, message: any): void;
21
21
  export function truncateText(value: any, maxLen?: number): string;
22
22
  export function sanitizeForLog(value: any, maxLen?: number): string;
23
+ /**
24
+ * Track tool calls that started but have not finished on a turn object, so
25
+ * `getCurrentTurnStatus()` can report what a long silent turn is running.
26
+ */
27
+ export function noteToolStarted(turn: any, id: any, name: any, input: any): void;
28
+ export function noteToolFinished(turn: any, id: any): void;
29
+ /** Map a codex app-server (camelCase) or exec (snake_case) tool item to `{ name, input }`. */
30
+ export function describeCodexToolItem(item: any): {
31
+ name: any;
32
+ input: any;
33
+ } | null;
34
+ export function withActiveTool(status: any, turn: any): any;
23
35
  export function isTruthyEnv(value: any): boolean;
24
36
  export function getBoundedEnvInt(envName: any, fallback: any, min: any, max: any): any;
25
37
  export function parseCommandParts(commandLine: any): {
@@ -87,6 +99,30 @@ export const TERMINAL_GOAL_STATUSES: ReadonlyArray<GoalStatus>;
87
99
  *
88
100
  * @typedef {Object} SessionCapabilities
89
101
  * @property {boolean} [goal]
102
+ * @property {boolean} [compact]
103
+ */
104
+ /**
105
+ * Manual context compaction (`/compact`) contract for providers that
106
+ * implement the optional `runCompact` method and advertise
107
+ * `capabilities.compact === true`. Providers must NOT emit assistant messages
108
+ * while compacting; the caller renders the user-facing confirmation.
109
+ *
110
+ * @typedef {Object} CompactRequest
111
+ * @property {string} [instructions] Optional focus instructions; providers
112
+ * whose backend cannot take them ignore them and report
113
+ * `instructionsApplied: false`.
114
+ *
115
+ * @typedef {Object} CompactState
116
+ * @property {"compacted"|"noop"} status `noop` = nothing to compact.
117
+ * @property {boolean} instructionsApplied
118
+ * @property {number} [preTokens]
119
+ * @property {number} [postTokens]
120
+ * @property {number} [tokensRemoved]
121
+ *
122
+ * @typedef {Object} CompactResult
123
+ * @property {CompactState} compact
124
+ * @property {unknown} [usage]
125
+ * @property {Record<string, unknown>} [metadata]
90
126
  */
91
127
  /**
92
128
  * Default capabilities assigned to providers that do not opt into any optional
@@ -175,4 +211,46 @@ export type GoalResult = {
175
211
  */
176
212
  export type SessionCapabilities = {
177
213
  goal?: boolean | undefined;
214
+ compact?: boolean | undefined;
215
+ };
216
+ /**
217
+ * Manual context compaction (`/compact`) contract for providers that
218
+ * implement the optional `runCompact` method and advertise
219
+ * `capabilities.compact === true`. Providers must NOT emit assistant messages
220
+ * while compacting; the caller renders the user-facing confirmation.
221
+ */
222
+ export type CompactRequest = {
223
+ /**
224
+ * Optional focus instructions; providers
225
+ * whose backend cannot take them ignore them and report
226
+ * `instructionsApplied: false`.
227
+ */
228
+ instructions?: string | undefined;
229
+ };
230
+ /**
231
+ * Manual context compaction (`/compact`) contract for providers that
232
+ * implement the optional `runCompact` method and advertise
233
+ * `capabilities.compact === true`. Providers must NOT emit assistant messages
234
+ * while compacting; the caller renders the user-facing confirmation.
235
+ */
236
+ export type CompactState = {
237
+ /**
238
+ * `noop` = nothing to compact.
239
+ */
240
+ status: "compacted" | "noop";
241
+ instructionsApplied: boolean;
242
+ preTokens?: number | undefined;
243
+ postTokens?: number | undefined;
244
+ tokensRemoved?: number | undefined;
245
+ };
246
+ /**
247
+ * Manual context compaction (`/compact`) contract for providers that
248
+ * implement the optional `runCompact` method and advertise
249
+ * `capabilities.compact === true`. Providers must NOT emit assistant messages
250
+ * while compacting; the caller renders the user-facing confirmation.
251
+ */
252
+ export type CompactResult = {
253
+ compact: CompactState;
254
+ usage?: unknown;
255
+ metadata?: Record<string, unknown> | undefined;
178
256
  };
package/dist/shared.js CHANGED
@@ -80,6 +80,30 @@ export function isTerminalGoalStatus(value) {
80
80
  *
81
81
  * @typedef {Object} SessionCapabilities
82
82
  * @property {boolean} [goal]
83
+ * @property {boolean} [compact]
84
+ */
85
+ /**
86
+ * Manual context compaction (`/compact`) contract for providers that
87
+ * implement the optional `runCompact` method and advertise
88
+ * `capabilities.compact === true`. Providers must NOT emit assistant messages
89
+ * while compacting; the caller renders the user-facing confirmation.
90
+ *
91
+ * @typedef {Object} CompactRequest
92
+ * @property {string} [instructions] Optional focus instructions; providers
93
+ * whose backend cannot take them ignore them and report
94
+ * `instructionsApplied: false`.
95
+ *
96
+ * @typedef {Object} CompactState
97
+ * @property {"compacted"|"noop"} status `noop` = nothing to compact.
98
+ * @property {boolean} instructionsApplied
99
+ * @property {number} [preTokens]
100
+ * @property {number} [postTokens]
101
+ * @property {number} [tokensRemoved]
102
+ *
103
+ * @typedef {Object} CompactResult
104
+ * @property {CompactState} compact
105
+ * @property {unknown} [usage]
106
+ * @property {Record<string, unknown>} [metadata]
83
107
  */
84
108
  /**
85
109
  * Default capabilities assigned to providers that do not opt into any optional
@@ -87,7 +111,7 @@ export function isTerminalGoalStatus(value) {
87
111
  *
88
112
  * @type {Readonly<SessionCapabilities>}
89
113
  */
90
- export const DEFAULT_SESSION_CAPABILITIES = Object.freeze({ goal: false });
114
+ export const DEFAULT_SESSION_CAPABILITIES = Object.freeze({ goal: false, compact: false });
91
115
  /**
92
116
  * Resolve a session's capability snapshot, falling back to
93
117
  * {@link DEFAULT_SESSION_CAPABILITIES}.
@@ -146,6 +170,76 @@ export function sanitizeForLog(value, maxLen = 180) {
146
170
  return "";
147
171
  return truncateText(String(value).replace(/\s+/g, " ").trim(), maxLen);
148
172
  }
173
+ const TOOL_INPUT_SUMMARY_KEYS = ["command", "cmd", "file_path", "filePath", "path", "pattern", "query", "url", "description", "prompt"];
174
+ function summarizeToolInput(input) {
175
+ let value = input;
176
+ if (typeof value === "string" && /^\s*[[{]/.test(value)) {
177
+ try {
178
+ value = JSON.parse(value);
179
+ }
180
+ catch {
181
+ // keep the raw string
182
+ }
183
+ }
184
+ if (value && typeof value === "object" && !Array.isArray(value)) {
185
+ const key = TOOL_INPUT_SUMMARY_KEYS.find((candidate) => value[candidate]);
186
+ value = key ? value[key] : JSON.stringify(value);
187
+ }
188
+ if (Array.isArray(value)) {
189
+ value = value.join(" ");
190
+ }
191
+ return sanitizeForLog(value, 200) || undefined;
192
+ }
193
+ /**
194
+ * Track tool calls that started but have not finished on a turn object, so
195
+ * `getCurrentTurnStatus()` can report what a long silent turn is running.
196
+ */
197
+ export function noteToolStarted(turn, id, name, input) {
198
+ if (!turn || !id || !name) {
199
+ return;
200
+ }
201
+ turn.activeTools ||= new Map();
202
+ const key = String(id);
203
+ if (turn.activeTools.has(key)) {
204
+ return;
205
+ }
206
+ turn.activeTools.set(key, {
207
+ name: String(name),
208
+ summary: summarizeToolInput(input),
209
+ started_at: new Date().toISOString(),
210
+ });
211
+ }
212
+ export function noteToolFinished(turn, id) {
213
+ if (id) {
214
+ turn?.activeTools?.delete(String(id));
215
+ }
216
+ }
217
+ /** Map a codex app-server (camelCase) or exec (snake_case) tool item to `{ name, input }`. */
218
+ export function describeCodexToolItem(item) {
219
+ switch (String(item?.type || "").replace(/_/g, "").toLowerCase()) {
220
+ case "commandexecution":
221
+ return { name: "command", input: item.command };
222
+ case "filechange":
223
+ return { name: "apply_patch", input: (item.changes || []).map((change) => change?.path).join(" ") };
224
+ case "mcptoolcall":
225
+ return { name: [item.server, item.tool].filter(Boolean).join("."), input: item.arguments };
226
+ case "dynamictoolcall":
227
+ case "collabagenttoolcall":
228
+ return { name: item.tool, input: item.arguments ?? item.prompt };
229
+ case "websearch":
230
+ return { name: "web_search", input: item.query };
231
+ default:
232
+ return null;
233
+ }
234
+ }
235
+ export function withActiveTool(status, turn) {
236
+ if (!status) {
237
+ return null;
238
+ }
239
+ const tools = turn?.activeTools ? [...turn.activeTools.values()] : [];
240
+ const activeTool = tools[tools.length - 1];
241
+ return activeTool ? { ...status, active_tool: { ...activeTool } } : { ...status };
242
+ }
149
243
  export function isTruthyEnv(value) {
150
244
  if (value === undefined || value === null)
151
245
  return false;
package/dist/worker.js CHANGED
@@ -76,7 +76,7 @@ async function handleRequest(message) {
76
76
  throw new Error(`Unsupported worker method: ${method}`);
77
77
  }
78
78
  const args = Array.isArray(message.args) ? [...message.args] : [];
79
- if (method === "runTurn" || method === "runGoal") {
79
+ if (method === "runTurn" || method === "runGoal" || method === "runCompact") {
80
80
  const firstArg = args[0];
81
81
  const options = args[1] && typeof args[1] === "object" ? { ...args[1] } : {};
82
82
  options.onProgress = (payload) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@love-moon/ai-sdk",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/lovemoon-ai/conductor.git"
@@ -53,12 +53,12 @@
53
53
  "zod": "^4.1.5"
54
54
  },
55
55
  "optionalDependencies": {
56
- "@love-moon/chat-web": "^0.13.0"
56
+ "@love-moon/chat-web": "^0.13.2"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@types/node": "^22.10.2",
60
60
  "tsx": "^4.20.6",
61
61
  "typescript": "^5.6.3"
62
62
  },
63
- "gitCommitId": "e1e507f"
63
+ "gitCommitId": "d60a6a2"
64
64
  }