@invergent/agent-chat-react 2.0.7 → 2.0.9

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/index.d.cts CHANGED
@@ -64,6 +64,11 @@ interface AgentChatMessage {
64
64
  errorInfo?: AgentChatErrorInfo;
65
65
  images?: AgentChatImageAttachment[];
66
66
  attachments?: AgentChatDisplayAttachment[];
67
+ llmResponseEventId?: number;
68
+ userFeedback?: {
69
+ rating: "up" | "down";
70
+ reason?: string;
71
+ };
67
72
  }
68
73
  interface AgentChatToolCallInfo {
69
74
  id: string;
@@ -275,7 +280,7 @@ interface AgentChatInboxEventStream {
275
280
  close(): void;
276
281
  onerror: (() => void) | null;
277
282
  }
278
- type AgentChatEventType = "user.message" | "llm.request" | "llm.response" | "llm.thinking" | "llm.delta" | "tool.call" | "tool.result" | "session.start" | "session.pause" | "session.resume" | "session.complete" | "session.fail" | "session.done" | "harness.wake" | "harness.crash" | "context.compact" | "skill.invoked" | "policy.denied" | "stream.timeout" | "expert.result" | "expert.endorse" | "expert.override" | "artifact.created" | "artifact.updated" | "browser.provisioned" | "browser.destroyed" | "browser.control_granted" | "browser.control_returned" | "clarify.response";
283
+ type AgentChatEventType = "user.message" | "llm.request" | "llm.response" | "llm.thinking" | "llm.delta" | "tool.call" | "tool.result" | "session.start" | "session.pause" | "session.resume" | "session.complete" | "session.fail" | "session.done" | "harness.wake" | "harness.crash" | "context.compact" | "skill.invoked" | "policy.denied" | "stream.timeout" | "expert.result" | "expert.endorse" | "expert.override" | "user.feedback" | "artifact.created" | "artifact.updated" | "browser.provisioned" | "browser.destroyed" | "browser.control_granted" | "browser.control_returned" | "clarify.response";
279
284
  interface AgentChatRuntimeEvent {
280
285
  type: AgentChatEventType;
281
286
  eventId: number;
@@ -550,6 +555,15 @@ interface AgentChatAdapter {
550
555
  eventId?: number;
551
556
  eventType?: string;
552
557
  }>;
558
+ submitUserFeedback?(input: {
559
+ sessionId: string;
560
+ llmResponseEventId: number;
561
+ rating: AgentChatExpertFeedbackRating;
562
+ reason?: string;
563
+ }): Promise<{
564
+ eventId?: number;
565
+ eventType?: string;
566
+ }>;
553
567
  listSlashCommands?(): Promise<AgentChatSlashCommand[]>;
554
568
  getWorkspaceTree(input: {
555
569
  sessionId: string;
package/dist/index.d.ts CHANGED
@@ -64,6 +64,11 @@ interface AgentChatMessage {
64
64
  errorInfo?: AgentChatErrorInfo;
65
65
  images?: AgentChatImageAttachment[];
66
66
  attachments?: AgentChatDisplayAttachment[];
67
+ llmResponseEventId?: number;
68
+ userFeedback?: {
69
+ rating: "up" | "down";
70
+ reason?: string;
71
+ };
67
72
  }
68
73
  interface AgentChatToolCallInfo {
69
74
  id: string;
@@ -275,7 +280,7 @@ interface AgentChatInboxEventStream {
275
280
  close(): void;
276
281
  onerror: (() => void) | null;
277
282
  }
278
- type AgentChatEventType = "user.message" | "llm.request" | "llm.response" | "llm.thinking" | "llm.delta" | "tool.call" | "tool.result" | "session.start" | "session.pause" | "session.resume" | "session.complete" | "session.fail" | "session.done" | "harness.wake" | "harness.crash" | "context.compact" | "skill.invoked" | "policy.denied" | "stream.timeout" | "expert.result" | "expert.endorse" | "expert.override" | "artifact.created" | "artifact.updated" | "browser.provisioned" | "browser.destroyed" | "browser.control_granted" | "browser.control_returned" | "clarify.response";
283
+ type AgentChatEventType = "user.message" | "llm.request" | "llm.response" | "llm.thinking" | "llm.delta" | "tool.call" | "tool.result" | "session.start" | "session.pause" | "session.resume" | "session.complete" | "session.fail" | "session.done" | "harness.wake" | "harness.crash" | "context.compact" | "skill.invoked" | "policy.denied" | "stream.timeout" | "expert.result" | "expert.endorse" | "expert.override" | "user.feedback" | "artifact.created" | "artifact.updated" | "browser.provisioned" | "browser.destroyed" | "browser.control_granted" | "browser.control_returned" | "clarify.response";
279
284
  interface AgentChatRuntimeEvent {
280
285
  type: AgentChatEventType;
281
286
  eventId: number;
@@ -550,6 +555,15 @@ interface AgentChatAdapter {
550
555
  eventId?: number;
551
556
  eventType?: string;
552
557
  }>;
558
+ submitUserFeedback?(input: {
559
+ sessionId: string;
560
+ llmResponseEventId: number;
561
+ rating: AgentChatExpertFeedbackRating;
562
+ reason?: string;
563
+ }): Promise<{
564
+ eventId?: number;
565
+ eventType?: string;
566
+ }>;
553
567
  listSlashCommands?(): Promise<AgentChatSlashCommand[]>;
554
568
  getWorkspaceTree(input: {
555
569
  sessionId: string;
package/dist/index.js CHANGED
@@ -6189,6 +6189,191 @@ function ChatComposerInner({
6189
6189
  }
6190
6190
  );
6191
6191
  }
6192
+ var MAX_REASON_LENGTH2 = 500;
6193
+ function TurnFeedback({ msg }) {
6194
+ const { adapter, sessionId } = useAgentChatAdapterContext();
6195
+ const [pending, setPending] = useState(
6196
+ null
6197
+ );
6198
+ const [error, setError] = useState(null);
6199
+ const [reasonDraft, setReasonDraft] = useState(null);
6200
+ const llmResponseEventId = msg.llmResponseEventId;
6201
+ const canRate = llmResponseEventId !== void 0 && msg.status === "complete" && sessionId !== null && adapter.submitUserFeedback !== void 0;
6202
+ if (!canRate) return null;
6203
+ const rating = msg.userFeedback?.rating ?? pending;
6204
+ const alreadyRated = msg.userFeedback !== void 0;
6205
+ const submit = async (next, reason) => {
6206
+ if (sessionId === null || llmResponseEventId === void 0 || adapter.submitUserFeedback === void 0) return;
6207
+ setPending(next);
6208
+ setError(null);
6209
+ try {
6210
+ await adapter.submitUserFeedback({
6211
+ sessionId,
6212
+ llmResponseEventId,
6213
+ rating: next,
6214
+ ...reason ? { reason } : {}
6215
+ });
6216
+ setReasonDraft(null);
6217
+ } catch (e) {
6218
+ setPending(null);
6219
+ setError(e instanceof Error ? e.message : "Failed to submit feedback");
6220
+ }
6221
+ };
6222
+ const handleRate = (next) => {
6223
+ if (alreadyRated || pending !== null) return;
6224
+ if (next === "down") {
6225
+ setReasonDraft("");
6226
+ return;
6227
+ }
6228
+ void submit("up");
6229
+ };
6230
+ return /* @__PURE__ */ jsxs("div", { className: "mt-1 space-y-1.5", children: [
6231
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
6232
+ /* @__PURE__ */ jsx(
6233
+ Button,
6234
+ {
6235
+ type: "button",
6236
+ variant: "ghost",
6237
+ size: "icon-xs",
6238
+ "aria-label": "Good response",
6239
+ title: alreadyRated && rating === "up" ? "Good response (recorded)" : "Good response",
6240
+ disabled: pending !== null || alreadyRated,
6241
+ onClick: () => handleRate("up"),
6242
+ className: cn(
6243
+ rating === "up" ? "text-foreground" : "text-muted-foreground/60"
6244
+ ),
6245
+ children: /* @__PURE__ */ jsx(ThumbsUpIcon, { className: "size-3.5" })
6246
+ }
6247
+ ),
6248
+ /* @__PURE__ */ jsx(
6249
+ Button,
6250
+ {
6251
+ type: "button",
6252
+ variant: "ghost",
6253
+ size: "icon-xs",
6254
+ "aria-label": "Poor response",
6255
+ title: alreadyRated && rating === "down" ? "Poor response (recorded)" : "Poor response",
6256
+ disabled: pending !== null || reasonDraft !== null || alreadyRated,
6257
+ onClick: () => handleRate("down"),
6258
+ className: cn(
6259
+ rating === "down" ? "text-foreground" : "text-muted-foreground/60"
6260
+ ),
6261
+ children: /* @__PURE__ */ jsx(ThumbsDownIcon, { className: "size-3.5" })
6262
+ }
6263
+ ),
6264
+ alreadyRated && msg.userFeedback?.reason && /* @__PURE__ */ jsxs(
6265
+ "span",
6266
+ {
6267
+ className: "text-muted-foreground/70 truncate max-w-xs",
6268
+ title: msg.userFeedback.reason,
6269
+ children: [
6270
+ '\xB7 "',
6271
+ msg.userFeedback.reason,
6272
+ '"'
6273
+ ]
6274
+ }
6275
+ ),
6276
+ error && /* @__PURE__ */ jsx("span", { className: "text-red-500 ml-1", children: error })
6277
+ ] }),
6278
+ reasonDraft !== null && !alreadyRated && /* @__PURE__ */ jsx(
6279
+ ReasonForm2,
6280
+ {
6281
+ initialValue: reasonDraft,
6282
+ busy: pending !== null,
6283
+ onSubmit: (reason) => void submit("down", reason),
6284
+ onCancel: () => {
6285
+ setReasonDraft(null);
6286
+ setError(null);
6287
+ }
6288
+ }
6289
+ )
6290
+ ] });
6291
+ }
6292
+ function ReasonForm2({
6293
+ initialValue,
6294
+ busy,
6295
+ onSubmit,
6296
+ onCancel
6297
+ }) {
6298
+ const [value, setValue] = useState(initialValue);
6299
+ const trimmed = value.trim();
6300
+ const charsLeft = MAX_REASON_LENGTH2 - value.length;
6301
+ const submit = () => {
6302
+ if (trimmed && !busy) onSubmit(trimmed);
6303
+ };
6304
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border bg-muted/30 p-2 space-y-1.5", children: [
6305
+ /* @__PURE__ */ jsx(
6306
+ "label",
6307
+ {
6308
+ className: "text-xs text-muted-foreground",
6309
+ htmlFor: "turn-feedback-reason",
6310
+ children: "What was wrong with the response?"
6311
+ }
6312
+ ),
6313
+ /* @__PURE__ */ jsx(
6314
+ Textarea,
6315
+ {
6316
+ id: "turn-feedback-reason",
6317
+ autoFocus: true,
6318
+ rows: 3,
6319
+ value,
6320
+ maxLength: MAX_REASON_LENGTH2,
6321
+ placeholder: "e.g. wrong answer, missed a constraint, hallucinated a fact\u2026",
6322
+ onChange: (e) => setValue(e.target.value),
6323
+ onKeyDown: (e) => {
6324
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
6325
+ e.preventDefault();
6326
+ submit();
6327
+ } else if (e.key === "Escape") {
6328
+ e.preventDefault();
6329
+ onCancel();
6330
+ }
6331
+ },
6332
+ className: "text-xs min-h-12",
6333
+ disabled: busy
6334
+ }
6335
+ ),
6336
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
6337
+ /* @__PURE__ */ jsxs(
6338
+ "span",
6339
+ {
6340
+ className: cn(
6341
+ "text-[10px] tabular-nums",
6342
+ charsLeft < 50 ? "text-amber-500" : "text-muted-foreground/60"
6343
+ ),
6344
+ children: [
6345
+ charsLeft,
6346
+ " characters left"
6347
+ ]
6348
+ }
6349
+ ),
6350
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
6351
+ /* @__PURE__ */ jsx(
6352
+ Button,
6353
+ {
6354
+ type: "button",
6355
+ variant: "ghost",
6356
+ size: "xs",
6357
+ onClick: onCancel,
6358
+ disabled: busy,
6359
+ children: "Cancel"
6360
+ }
6361
+ ),
6362
+ /* @__PURE__ */ jsx(
6363
+ Button,
6364
+ {
6365
+ type: "button",
6366
+ variant: "default",
6367
+ size: "xs",
6368
+ onClick: submit,
6369
+ disabled: busy || !trimmed,
6370
+ children: busy ? "Sending\u2026" : "Send feedback"
6371
+ }
6372
+ )
6373
+ ] })
6374
+ ] })
6375
+ ] });
6376
+ }
6192
6377
  var MIN_CPS = 40;
6193
6378
  var MAX_CPS = 240;
6194
6379
  var DRAIN_SECONDS = 0.25;
@@ -6839,7 +7024,8 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
6839
7024
  kind: "text",
6840
7025
  key: `${msg.id}-text`,
6841
7026
  content: msg.content,
6842
- isStreaming
7027
+ isStreaming,
7028
+ msg
6843
7029
  });
6844
7030
  }
6845
7031
  const hasRunningTool = hasToolCalls && msg.toolCalls.some(
@@ -7142,7 +7328,10 @@ function TextEntry({
7142
7328
  /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7143
7329
  /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2 border-none bg-foreground/40" })
7144
7330
  ] }),
7145
- /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(MessageResponse, { children: content }) })
7331
+ /* @__PURE__ */ jsxs(TimelineContent, { children: [
7332
+ /* @__PURE__ */ jsx(MessageResponse, { children: content }),
7333
+ entry.isFinalTurnText && entry.msg.status === "complete" && /* @__PURE__ */ jsx(TurnFeedback, { msg: entry.msg })
7334
+ ] })
7146
7335
  ] });
7147
7336
  }
7148
7337
  function AssistantGroup({
@@ -7162,6 +7351,13 @@ function AssistantGroup({
7162
7351
  }
7163
7352
  entries = groupBrowserActivityEntries(entries);
7164
7353
  entries = groupWebSearchEntries(entries);
7354
+ for (let i = entries.length - 1; i >= 0; i--) {
7355
+ const entry = entries[i];
7356
+ if (entry?.kind === "text") {
7357
+ entries[i] = { ...entry, isFinalTurnText: true };
7358
+ break;
7359
+ }
7360
+ }
7165
7361
  const isTailGroup = lastGlobalIndex === totalMessages - 1;
7166
7362
  const lastEntry = entries[entries.length - 1];
7167
7363
  const hasRunningTool = entries.some(
@@ -8471,6 +8667,7 @@ var AGENT_CHAT_LISTENED_EVENTS = [
8471
8667
  "expert.result",
8472
8668
  "expert.endorse",
8473
8669
  "expert.override",
8670
+ "user.feedback",
8474
8671
  "artifact.created",
8475
8672
  "artifact.updated",
8476
8673
  "browser.provisioned",
@@ -8629,6 +8826,11 @@ function applyAgentChatEvent(state, event) {
8629
8826
  nextState,
8630
8827
  applyExpertFeedback(nextState.messages, event.type, event.data)
8631
8828
  );
8829
+ case "user.feedback":
8830
+ return withMessages(
8831
+ nextState,
8832
+ applyUserFeedback(nextState.messages, event.data)
8833
+ );
8632
8834
  case "clarify.response":
8633
8835
  return withMessages(
8634
8836
  nextState,
@@ -8826,17 +9028,23 @@ function applyLlmResponse(state, event) {
8826
9028
  ...current,
8827
9029
  reasoning: (current.reasoning ?? "") + current.content,
8828
9030
  content: "",
8829
- status: "streaming"
9031
+ status: "streaming",
9032
+ llmResponseEventId: event.eventId
8830
9033
  };
8831
9034
  } else {
8832
- messages[idx] = { ...current, status: "complete" };
9035
+ messages[idx] = {
9036
+ ...current,
9037
+ status: "complete",
9038
+ llmResponseEventId: event.eventId
9039
+ };
8833
9040
  }
8834
9041
  } else if (matchesExistingToolTurn && idx >= 0) {
8835
9042
  const current = messages[idx];
8836
9043
  messages[idx] = {
8837
9044
  ...current,
8838
9045
  reasoning: responseContent ? appendText(current.reasoning, responseContent) : current.reasoning,
8839
- status: "streaming"
9046
+ status: "streaming",
9047
+ llmResponseEventId: event.eventId
8840
9048
  };
8841
9049
  } else if (prevHasTools || !prevAssistant || hasUserAfter) {
8842
9050
  messages.push({
@@ -8845,7 +9053,8 @@ function applyLlmResponse(state, event) {
8845
9053
  content: hasToolCalls ? "" : responseContent,
8846
9054
  reasoning: hasToolCalls && responseContent ? responseContent : void 0,
8847
9055
  createdAt: /* @__PURE__ */ new Date(),
8848
- status: hasToolCalls ? "streaming" : "complete"
9056
+ status: hasToolCalls ? "streaming" : "complete",
9057
+ llmResponseEventId: event.eventId
8849
9058
  });
8850
9059
  } else {
8851
9060
  const current = messages[idx];
@@ -8853,13 +9062,15 @@ function applyLlmResponse(state, event) {
8853
9062
  messages[idx] = {
8854
9063
  ...current,
8855
9064
  reasoning: (current.reasoning ?? "") + responseContent,
8856
- status: "streaming"
9065
+ status: "streaming",
9066
+ llmResponseEventId: event.eventId
8857
9067
  };
8858
9068
  } else {
8859
9069
  messages[idx] = {
8860
9070
  ...current,
8861
9071
  content: responseContent || current.content,
8862
- status: hasToolCalls ? "streaming" : "complete"
9072
+ status: hasToolCalls ? "streaming" : "complete",
9073
+ llmResponseEventId: event.eventId
8863
9074
  };
8864
9075
  }
8865
9076
  }
@@ -9072,6 +9283,26 @@ function applyExpertFeedback(messages, type, data) {
9072
9283
  }
9073
9284
  return messages;
9074
9285
  }
9286
+ function applyUserFeedback(messages, data) {
9287
+ const source = typeof data.source === "string" ? data.source : "user";
9288
+ if (source !== "user") return messages;
9289
+ const targetEventId = typeof data.target_event_id === "number" ? data.target_event_id : void 0;
9290
+ if (targetEventId == null) return messages;
9291
+ const rating = data.rating === "down" ? "down" : "up";
9292
+ const reason = typeof data.reason === "string" ? data.reason : void 0;
9293
+ for (let i = messages.length - 1; i >= 0; i--) {
9294
+ const msg = messages[i];
9295
+ if (msg?.role !== "assistant") continue;
9296
+ if (msg.llmResponseEventId !== targetEventId) continue;
9297
+ if (msg.userFeedback?.rating === rating && msg.userFeedback?.reason === reason) {
9298
+ return messages;
9299
+ }
9300
+ const next = [...messages];
9301
+ next[i] = { ...msg, userFeedback: { rating, reason } };
9302
+ return next;
9303
+ }
9304
+ return messages;
9305
+ }
9075
9306
  function applyClarifyResponse(messages, data) {
9076
9307
  const targetToolId = stringValue2(data.tool_call_id);
9077
9308
  const responses = Array.isArray(data.responses) ? data.responses : void 0;
@@ -11186,7 +11417,6 @@ function MetadataPane({ mission }) {
11186
11417
  ] });
11187
11418
  }
11188
11419
  var DEFAULT_POLL_INTERVAL_MS2 = 3e4;
11189
- var DEFAULT_STATUS = "active,paused";
11190
11420
  function fingerprint(items) {
11191
11421
  return items.map(
11192
11422
  (m) => `${m.id}:${m.status}:${m.iteration}:${m.lastEvaluationResult ?? ""}:${m.lastEvaluationAt ?? ""}:${m.pausedReason ?? ""}:${m.updatedAt}`
@@ -11312,7 +11542,7 @@ function MissionsPanel({
11312
11542
  adapter,
11313
11543
  agentId,
11314
11544
  title = "Missions",
11315
- status = DEFAULT_STATUS,
11545
+ status,
11316
11546
  hideHeader = false,
11317
11547
  pollIntervalMs = DEFAULT_POLL_INTERVAL_MS2,
11318
11548
  onMissionSelect,