@syntrologie/adapt-chatbot 2.8.0-canary.292 → 2.8.0-canary.293

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.
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  AdaptiveChatBar
3
3
  } from "./chunk-SCVTTLFJ.js";
4
- import "./chunk-4P74RX6V.js";
5
- import "./chunk-UC4XU6GH.js";
4
+ import "./chunk-YQU3O7VK.js";
5
+ import "./chunk-C6K7W3LO.js";
6
6
  import "./chunk-UVKRO5ER.js";
7
7
  export {
8
8
  AdaptiveChatBar
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  AdaptiveChatTrail
3
- } from "./chunk-4P74RX6V.js";
4
- import "./chunk-UC4XU6GH.js";
3
+ } from "./chunk-YQU3O7VK.js";
4
+ import "./chunk-C6K7W3LO.js";
5
5
  import "./chunk-UVKRO5ER.js";
6
6
  export {
7
7
  AdaptiveChatTrail
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  ChatAssistantLitMountable,
3
3
  renderFallbackHtml
4
- } from "./chunk-IQKCST6R.js";
5
- import "./chunk-UC4XU6GH.js";
4
+ } from "./chunk-FLSMUFFX.js";
5
+ import "./chunk-C6K7W3LO.js";
6
6
  import "./chunk-VLJ3WOEX.js";
7
7
  import "./chunk-UVKRO5ER.js";
8
8
  export {
@@ -223,8 +223,8 @@ var _createTrustedTypesPolicy = function _createTrustedTypesPolicy2(trustedTypes
223
223
  const policyName = "dompurify" + (suffix ? "#" + suffix : "");
224
224
  try {
225
225
  return trustedTypes.createPolicy(policyName, {
226
- createHTML(html8) {
227
- return html8;
226
+ createHTML(html9) {
227
+ return html9;
228
228
  },
229
229
  createScriptURL(scriptUrl) {
230
230
  return scriptUrl;
@@ -2238,6 +2238,7 @@ var en = x.lex;
2238
2238
  import { html as html22 } from "lit";
2239
2239
  import { html as html3, nothing as nothing2 } from "lit";
2240
2240
  import { html as html5 } from "lit";
2241
+ import { html as html8, LitElement as LitElement2 } from "lit";
2241
2242
  var sendIcon = html2`<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 14V2M8 2L3 7M8 2L13 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
2242
2243
  var stopIcon = html2`<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="1" y="1" width="12" height="12" rx="2" fill="currentColor"/></svg>`;
2243
2244
  function autoResize(e) {
@@ -2369,18 +2370,20 @@ function renderBubbleContent(message) {
2369
2370
  return html4`<div data-sc-msg-assistant>${unsafeHTML(renderMarkdown(message.content))}</div>`;
2370
2371
  }
2371
2372
  function renderMessageBubble(props) {
2372
- const { message, toolLabels, toolRenderers, defaultToolRenderer } = props;
2373
+ const { message, toolLabels, toolRenderers, toolContext, defaultToolRenderer } = props;
2373
2374
  const isUser = message.role === "user";
2374
2375
  const toolCards = message.toolCalls?.map((tc) => {
2375
- const approve = () => props.onToolApprove(tc.id);
2376
+ const approve = (payload) => props.onToolApprove(tc.id, payload);
2376
2377
  const deny = () => props.onToolDeny(tc.id);
2378
+ const context = toolContext?.[tc.name];
2377
2379
  const customRenderer = toolRenderers?.[tc.name];
2378
2380
  if (customRenderer) {
2379
2381
  return customRenderer.render({
2380
2382
  toolCall: tc,
2381
2383
  phase: tc.status,
2382
2384
  approve,
2383
- deny
2385
+ deny,
2386
+ context
2384
2387
  });
2385
2388
  }
2386
2389
  if (defaultToolRenderer) {
@@ -2388,13 +2391,14 @@ function renderMessageBubble(props) {
2388
2391
  toolCall: tc,
2389
2392
  phase: tc.status,
2390
2393
  approve,
2391
- deny
2394
+ deny,
2395
+ context
2392
2396
  });
2393
2397
  }
2394
2398
  return renderToolCallCard({
2395
2399
  toolCall: tc,
2396
2400
  toolLabel: toolLabels?.[tc.name],
2397
- approve,
2401
+ approve: () => approve(),
2398
2402
  deny
2399
2403
  });
2400
2404
  });
@@ -2435,11 +2439,12 @@ function renderMessageList(props) {
2435
2439
  props.messages,
2436
2440
  (msg) => msg.id,
2437
2441
  (msg) => guard(
2438
- [fingerprint(msg)],
2442
+ [fingerprint(msg), props.toolContext],
2439
2443
  () => renderMessageBubble({
2440
2444
  message: msg,
2441
2445
  toolLabels: props.toolLabels,
2442
2446
  toolRenderers: props.toolRenderers,
2447
+ toolContext: props.toolContext,
2443
2448
  defaultToolRenderer: props.defaultToolRenderer,
2444
2449
  onCopy: props.onCopy,
2445
2450
  onRegenerate: props.onRegenerate,
@@ -2990,6 +2995,19 @@ var SyntroChat = class extends LitElement {
2990
2995
  if (event.isTyping) this._scrollToBottom();
2991
2996
  break;
2992
2997
  case "tool-call": {
2998
+ const hasParent = this._messages.some((m2) => m2.id === event.messageId);
2999
+ if (!hasParent) {
3000
+ this._messages = [
3001
+ ...this._messages,
3002
+ {
3003
+ id: event.messageId,
3004
+ role: "assistant",
3005
+ content: "",
3006
+ timestamp: Date.now(),
3007
+ status: "streaming"
3008
+ }
3009
+ ];
3010
+ }
2993
3011
  this._messages = this._messages.map((m2) => {
2994
3012
  if (m2.id !== event.messageId) return m2;
2995
3013
  const existing = (m2.toolCalls ?? []).findIndex((tc) => tc.id === event.toolCall.id);
@@ -3038,6 +3056,50 @@ var SyntroChat = class extends LitElement {
3038
3056
  });
3039
3057
  break;
3040
3058
  }
3059
+ case "tool-call-deferred": {
3060
+ this._messages = this._messages.map((m2) => {
3061
+ if (!m2.toolCalls) return m2;
3062
+ const toolCalls = m2.toolCalls.map(
3063
+ (tc) => tc.id === event.toolCallId ? { ...tc, interruptId: event.interruptId, status: "pending" } : tc
3064
+ );
3065
+ return { ...m2, toolCalls };
3066
+ });
3067
+ break;
3068
+ }
3069
+ case "tool-call-cancelled": {
3070
+ this._messages = this._messages.map((m2) => {
3071
+ if (!m2.toolCalls) return m2;
3072
+ const toolCalls = m2.toolCalls.map(
3073
+ (tc) => tc.id === event.toolCallId ? { ...tc, status: "cancelled" } : tc
3074
+ );
3075
+ return { ...m2, toolCalls };
3076
+ });
3077
+ break;
3078
+ }
3079
+ case "tool-call-result": {
3080
+ let foundToolName;
3081
+ this._messages = this._messages.map((m2) => {
3082
+ if (!m2.toolCalls) return m2;
3083
+ const toolCalls = m2.toolCalls.map((tc) => {
3084
+ if (tc.id !== event.toolCallId) return tc;
3085
+ foundToolName = tc.name;
3086
+ return { ...tc, result: event.result, status: "done" };
3087
+ });
3088
+ return { ...m2, toolCalls };
3089
+ });
3090
+ this.dispatchEvent(
3091
+ new CustomEvent("tool-result-received", {
3092
+ detail: {
3093
+ toolCallId: event.toolCallId,
3094
+ toolName: foundToolName,
3095
+ result: event.result
3096
+ },
3097
+ bubbles: true,
3098
+ composed: true
3099
+ })
3100
+ );
3101
+ break;
3102
+ }
3041
3103
  case "error":
3042
3104
  this._errorMessage = event.message;
3043
3105
  break;
@@ -3074,16 +3136,45 @@ var SyntroChat = class extends LitElement {
3074
3136
  this._onRegenerate = (messageId) => {
3075
3137
  this.transport.send({ type: "regenerate", messageId });
3076
3138
  };
3077
- this._onToolApprove = (toolCallId) => {
3139
+ this._onToolApprove = (toolCallId, payload, _retryCount = 0) => {
3140
+ const interruptId = this._findInterruptId(toolCallId);
3141
+ if (interruptId) {
3142
+ this._markToolDone(toolCallId);
3143
+ this.transport.send({
3144
+ type: "deferred-tool-resume",
3145
+ interruptId,
3146
+ decision: "approve",
3147
+ ...payload?.editedArgs ? { editedArgs: payload.editedArgs } : {}
3148
+ });
3149
+ return;
3150
+ }
3151
+ if (_retryCount < 10) {
3152
+ setTimeout(() => this._onToolApprove(toolCallId, payload, _retryCount + 1), 200);
3153
+ return;
3154
+ }
3078
3155
  this._markToolDone(toolCallId);
3079
3156
  this.transport.send({
3080
3157
  type: "tool-result",
3081
3158
  toolCallId,
3082
- result: null,
3159
+ result: payload?.editedArgs ?? null,
3083
3160
  approved: true
3084
3161
  });
3085
3162
  };
3086
- this._onToolDeny = (toolCallId) => {
3163
+ this._onToolDeny = (toolCallId, _retryCount = 0) => {
3164
+ const interruptId = this._findInterruptId(toolCallId);
3165
+ if (interruptId) {
3166
+ this._markToolDone(toolCallId);
3167
+ this.transport.send({
3168
+ type: "deferred-tool-resume",
3169
+ interruptId,
3170
+ decision: "deny"
3171
+ });
3172
+ return;
3173
+ }
3174
+ if (_retryCount < 10) {
3175
+ setTimeout(() => this._onToolDeny(toolCallId, _retryCount + 1), 200);
3176
+ return;
3177
+ }
3087
3178
  this._markToolDone(toolCallId);
3088
3179
  this.transport.send({
3089
3180
  type: "tool-result",
@@ -3102,6 +3193,47 @@ var SyntroChat = class extends LitElement {
3102
3193
  this.sendUserMessage(text2);
3103
3194
  };
3104
3195
  }
3196
+ // ── Public API: thread navigation ──────────────────────────────────
3197
+ //
3198
+ // Composing elements (the per-surface wrappers like
3199
+ // <ap-action-plan-chat>) call these to load past threads and start
3200
+ // new ones. The chat element doesn't own the thread index — that's
3201
+ // the consumer's job (typically a sibling <syntro-chat-thread-list>
3202
+ // + the consumer's REST endpoint). This element just owns the
3203
+ // visible message state.
3204
+ /**
3205
+ * Replace the chat's message history wholesale. Used by the host
3206
+ * shell when switching threads (after fetching the new thread's
3207
+ * messages from persistence) or when restoring a session.
3208
+ *
3209
+ * Emits `chat-messages-replaced` so dependent UI (overlay cards,
3210
+ * action plan sidebar) can react.
3211
+ */
3212
+ replaceMessages(messages) {
3213
+ this._messages = [...messages];
3214
+ this._isStreaming = messages.some((m2) => m2.status === "streaming");
3215
+ this._userScrolledUp = false;
3216
+ requestAnimationFrame(() => this._scrollToBottom());
3217
+ this.dispatchEvent(
3218
+ new CustomEvent("chat-messages-replaced", {
3219
+ detail: { messageCount: messages.length },
3220
+ bubbles: true,
3221
+ composed: true
3222
+ })
3223
+ );
3224
+ }
3225
+ /**
3226
+ * Clear the chat for a fresh conversation. Equivalent to
3227
+ * `replaceMessages([])` plus a state reset.
3228
+ */
3229
+ reset() {
3230
+ this._messages = [];
3231
+ this._isStreaming = false;
3232
+ this._isTyping = false;
3233
+ this._inputValue = "";
3234
+ this._userScrolledUp = false;
3235
+ this._errorMessage = null;
3236
+ }
3105
3237
  createRenderRoot() {
3106
3238
  return this;
3107
3239
  }
@@ -3198,6 +3330,13 @@ var SyntroChat = class extends LitElement {
3198
3330
  this._messages = [...this._messages, msg];
3199
3331
  this._scrollToBottom();
3200
3332
  }
3333
+ _findInterruptId(toolCallId) {
3334
+ for (const m2 of this._messages) {
3335
+ const tc = m2.toolCalls?.find((t) => t.id === toolCallId);
3336
+ if (tc?.interruptId) return tc.interruptId;
3337
+ }
3338
+ return void 0;
3339
+ }
3201
3340
  _markToolDone(toolCallId) {
3202
3341
  this._messages = this._messages.map((m2) => {
3203
3342
  if (!m2.toolCalls) return m2;
@@ -3237,6 +3376,7 @@ var SyntroChat = class extends LitElement {
3237
3376
  isTyping: this._isTyping,
3238
3377
  toolLabels: this.toolLabels,
3239
3378
  toolRenderers: this.toolRenderers,
3379
+ toolContext: this.toolContext,
3240
3380
  defaultToolRenderer: this.defaultToolRenderer,
3241
3381
  onCopy: this._onCopy,
3242
3382
  onRegenerate: this._onRegenerate,
@@ -3277,6 +3417,7 @@ SyntroChat.properties = {
3277
3417
  suggestions: { attribute: false },
3278
3418
  toolLabels: { attribute: false },
3279
3419
  toolRenderers: { attribute: false },
3420
+ toolContext: { attribute: false },
3280
3421
  defaultToolRenderer: { attribute: false },
3281
3422
  autoExecTools: { attribute: false },
3282
3423
  placeholder: { type: String },
@@ -3291,6 +3432,117 @@ SyntroChat.properties = {
3291
3432
  _userScrolledUp: { state: true },
3292
3433
  _errorMessage: { state: true }
3293
3434
  };
3435
+ var SyntroChatThreadList = class extends LitElement2 {
3436
+ constructor() {
3437
+ super(...arguments);
3438
+ this.threadsEndpoint = "";
3439
+ this.activeThreadId = null;
3440
+ this.newThreadLabel = "New thread";
3441
+ this.emptyStateLabel = "No threads yet";
3442
+ this._threads = [];
3443
+ this._loading = false;
3444
+ this._error = null;
3445
+ this._onThreadClick = (threadId) => {
3446
+ if (threadId === this.activeThreadId) return;
3447
+ this.dispatchEvent(
3448
+ new CustomEvent("thread-selected", {
3449
+ detail: { threadId },
3450
+ bubbles: true,
3451
+ composed: true
3452
+ })
3453
+ );
3454
+ };
3455
+ }
3456
+ createRenderRoot() {
3457
+ return this;
3458
+ }
3459
+ connectedCallback() {
3460
+ super.connectedCallback();
3461
+ if (this.threadsEndpoint) void this.refresh();
3462
+ }
3463
+ updated(changed) {
3464
+ if (changed.has("threadsEndpoint") && this.threadsEndpoint) {
3465
+ void this.refresh();
3466
+ }
3467
+ }
3468
+ /** Re-fetch the thread index. Call after creating/archiving a thread. */
3469
+ async refresh() {
3470
+ if (!this.threadsEndpoint) return;
3471
+ this._loading = true;
3472
+ this._error = null;
3473
+ try {
3474
+ const res = await fetch(this.threadsEndpoint, {
3475
+ method: "GET",
3476
+ headers: { ...this.authHeaders ?? {}, Accept: "application/json" }
3477
+ });
3478
+ if (!res.ok) {
3479
+ this._error = `Failed to load threads (${res.status})`;
3480
+ this._threads = [];
3481
+ return;
3482
+ }
3483
+ const data = await res.json();
3484
+ this._threads = (data.threads ?? []).filter((t) => t.status !== "archived");
3485
+ } catch (e) {
3486
+ this._error = e instanceof Error ? e.message : "Failed to load threads";
3487
+ this._threads = [];
3488
+ } finally {
3489
+ this._loading = false;
3490
+ }
3491
+ }
3492
+ /** Imperative: request the host to start a new thread.
3493
+ * Fires `new-thread-requested`. The host clears the chat element and
3494
+ * drops `activeThreadId` so the next user message goes to a fresh
3495
+ * backend thread.
3496
+ */
3497
+ createNewThread() {
3498
+ this.dispatchEvent(new CustomEvent("new-thread-requested", { bubbles: true, composed: true }));
3499
+ }
3500
+ render() {
3501
+ return html8`
3502
+ <div data-sc-thread-list>
3503
+ <button
3504
+ type="button"
3505
+ data-sc-new-thread-btn
3506
+ @click=${() => this.createNewThread()}
3507
+ aria-label=${this.newThreadLabel}
3508
+ >
3509
+ ${this.newThreadLabel}
3510
+ </button>
3511
+ ${this._loading ? html8`<div data-sc-thread-list-loading>Loading…</div>` : this._error ? html8`<div data-sc-thread-list-error>${this._error}</div>` : this._threads.length === 0 ? html8`<div data-sc-thread-list-empty>${this.emptyStateLabel}</div>` : html8`<ul data-sc-thread-list-items role="list">
3512
+ ${this._threads.map(
3513
+ (t) => html8`
3514
+ <li
3515
+ data-sc-thread-list-item
3516
+ ?data-sc-active=${t.remoteId === this.activeThreadId}
3517
+ >
3518
+ <button
3519
+ type="button"
3520
+ @click=${() => this._onThreadClick(t.remoteId)}
3521
+ title=${t.title ?? t.remoteId}
3522
+ >
3523
+ ${t.title || "Untitled thread"}
3524
+ </button>
3525
+ </li>
3526
+ `
3527
+ )}
3528
+ </ul>`}
3529
+ </div>
3530
+ `;
3531
+ }
3532
+ };
3533
+ SyntroChatThreadList.properties = {
3534
+ threadsEndpoint: { type: String, attribute: "threads-endpoint" },
3535
+ authHeaders: { attribute: false },
3536
+ activeThreadId: { type: String, attribute: "active-thread-id" },
3537
+ newThreadLabel: { type: String, attribute: "new-thread-label" },
3538
+ emptyStateLabel: { type: String, attribute: "empty-state-label" },
3539
+ _threads: { state: true },
3540
+ _loading: { state: true },
3541
+ _error: { state: true }
3542
+ };
3543
+ if (!customElements.get("syntro-chat-thread-list")) {
3544
+ customElements.define("syntro-chat-thread-list", SyntroChatThreadList);
3545
+ }
3294
3546
  if (!customElements.get("syntro-chat")) {
3295
3547
  customElements.define("syntro-chat", SyntroChat);
3296
3548
  }
@@ -3303,4 +3555,4 @@ export {
3303
3555
  dompurify/dist/purify.es.mjs:
3304
3556
  (*! @license DOMPurify 3.3.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.1/LICENSE *)
3305
3557
  */
3306
- //# sourceMappingURL=chunk-UC4XU6GH.js.map
3558
+ //# sourceMappingURL=chunk-C6K7W3LO.js.map