@m6d/cortex-angular 0.12.0 → 0.14.0

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/README.md CHANGED
@@ -42,7 +42,7 @@ No root-level providers are needed. Each widget is self-contained, so one page c
42
42
  | `viewMode` | `'helper' \| 'full'` | Compact panel or full layout with a sidebar (default: `'helper'`) |
43
43
  | `theme` | `'light' \| 'dark' \| 'system'` | Color scheme (default: `'light'`) |
44
44
  | `agentName` | `string \| { en, ar }` | Header title while no thread is selected (optional) |
45
- | `showDebugButton` | `boolean` | Show/hide the debug toggle button (default: `false`) |
45
+ | `thread` | `string` | A thread id to open on mount (optional) |
46
46
  | `mediaRenderers` | `Record<string, MediaRenderer>` | Replace or add the media fences the assistant can draw (optional) |
47
47
  | `trustSameOriginEmbeds` | `boolean` | Allow embedded tool pages served from this page's own origin (default: `false`) |
48
48
  | `hooks.onToolCall` | `(toolCall, context) => unknown` | Answers a client tool; return `undefined` for tools you do not own (optional) |
@@ -51,7 +51,7 @@ No root-level providers are needed. Each widget is self-contained, so one page c
51
51
  | `toolComponents` | `Record<string, Type<CortexToolComponentInputs>>` | Custom Angular components for tool rendering (optional) |
52
52
  | `injector` | `Injector` | Injector for `toolComponents` (optional) |
53
53
 
54
- `transport.getHeaders` returns the headers every call carries, usually a bearer `Authorization`. Called straight from another origin, the engine accepts only `Authorization` and the headers the widget sets itself; route anything else through a same-origin proxy. The WebSocket takes its `token` query param from a bearer `Authorization` header when there is one.
54
+ `transport.getHeaders` returns the headers every call carries, usually a bearer `Authorization`. Called straight from another origin, the engine accepts only `Authorization` and the headers the widget sets itself; route anything else through a same-origin proxy. The WebSocket takes its `token` query param from a bearer `Authorization` header when there is one. The debug toggle shows only when Cortex offers this caller the debug view: always to a console operator, and to everyone only where the engine runs with `ENGINE_DEBUG=true`. Anyone else gets only what the widget's normal view draws, and so does a `toolComponents` entry for one of the agent's server tools: the model's line for the user as the call's input, and only whether it failed (or a dataset read's row count) as its output.
55
55
 
56
56
  ## Custom parts
57
57
 
@@ -119,19 +119,19 @@ for typing your own rows and chips.
119
119
 
120
120
  One header serves both screens; `screen` says which is showing.
121
121
 
122
- | Field | Type | Description |
123
- | -------------- | --------------------- | ------------------------------------------------------------------ |
124
- | `screen` | `'chat' \| 'threads'` | Which screen the header is on |
125
- | `viewMode` | `'helper' \| 'full'` | The configured view mode |
126
- | `title` | `string \| null` | The open thread's title; `null` on a new chat |
127
- | `agentName` | `string \| undefined` | `agentName` in the widget's locale |
128
- | `threadCount` | `number \| undefined` | How many conversations the history lists; undefined while it loads |
129
- | `isWorking` | `boolean` | The agent is answering |
130
- | `debug` | `{ shown, on }` | Whether `showDebugButton` is set, and whether debug mode is on |
131
- | `newChat` | `() => void` | Starts a new chat |
132
- | `openHistory` | `() => void` | Shows the history: its screen, or the sidebar in full mode |
133
- | `closeHistory` | `() => void` | Returns to the open chat |
134
- | `toggleDebug` | `() => void` | Switches debug mode |
122
+ | Field | Type | Description |
123
+ | -------------- | --------------------- | -------------------------------------------------------------------- |
124
+ | `screen` | `'chat' \| 'threads'` | Which screen the header is on |
125
+ | `viewMode` | `'helper' \| 'full'` | The configured view mode |
126
+ | `title` | `string \| null` | The open thread's title; `null` on a new chat |
127
+ | `agentName` | `string \| undefined` | `agentName` in the widget's locale |
128
+ | `threadCount` | `number \| undefined` | How many conversations the history lists; undefined while it loads |
129
+ | `isWorking` | `boolean` | The agent is answering |
130
+ | `debug` | `{ shown, on }` | Whether Cortex offers the debug view, and whether it is on |
131
+ | `newChat` | `() => void` | Starts a new chat; absent until Cortex confirms the caller may write |
132
+ | `openHistory` | `() => void` | Shows the history: its screen, or the sidebar in full mode |
133
+ | `closeHistory` | `() => void` | Returns to the open chat |
134
+ | `toggleDebug` | `() => void` | Switches debug mode |
135
135
 
136
136
  ### `landing`
137
137
 
@@ -152,13 +152,13 @@ landing for it.
152
152
 
153
153
  The history's list. Its header is the `header` slot.
154
154
 
155
- | Field | Type | Description |
156
- | ------------------ | --------------------------------- | ----------------------------------------------------- |
157
- | `threads` | `ThreadSummary[] \| undefined` | Newest first; `undefined` while the first load is out |
158
- | `selectedThreadId` | `string \| undefined` | The open thread's id; `undefined` on a new chat |
159
- | `openThread` | `(thread: ThreadSummary) => void` | Opens a thread |
160
- | `deleteThread` | `(threadId: string) => void` | Deletes a thread |
161
- | `newChat` | `() => void` | Starts a new chat |
155
+ | Field | Type | Description |
156
+ | ------------------ | --------------------------------- | -------------------------------------------------------------------- |
157
+ | `threads` | `ThreadSummary[] \| undefined` | Newest first; `undefined` while the first load is out |
158
+ | `selectedThreadId` | `string \| undefined` | The open thread's id; `undefined` on a new chat |
159
+ | `openThread` | `(thread: ThreadSummary) => void` | Opens a thread |
160
+ | `deleteThread` | `(threadId: string) => void` | Deletes a thread; absent until Cortex confirms the caller may write |
161
+ | `newChat` | `() => void` | Starts a new chat; absent until Cortex confirms the caller may write |
162
162
 
163
163
  ## Theming
164
164
 
@@ -48,6 +48,13 @@ function checkAttachmentPolicy(file) {
48
48
  return "ok";
49
49
  }
50
50
  var STATEMENT_TOOLS = ["executeCode", "queryDataset", "describeDataset"];
51
+ var APPROVAL_TITLE_FIELD = "approvalTitle";
52
+ function approvalTitleOf(input) {
53
+ if (!input || typeof input !== "object" || !(APPROVAL_TITLE_FIELD in input))
54
+ return;
55
+ const title = input[APPROVAL_TITLE_FIELD];
56
+ return typeof title === "string" && title.trim() ? title.trim() : undefined;
57
+ }
51
58
  function toolCallMetadataOf(part) {
52
59
  const metadata = part.metadata;
53
60
  return metadata?.cortex ?? undefined;
@@ -60,6 +67,8 @@ function approvalIdOf(part) {
60
67
  return part.approval?.id ?? `approval_${part.id}`;
61
68
  }
62
69
  var TIMEZONE_HEADER = "X-Cortex-Timezone";
70
+ var OPERATOR_HEADER = "X-Cortex-Operator";
71
+ var INSPECT_HEADER = "X-Cortex-Inspect";
63
72
 
64
73
  // src/internal/client/src/chat/api-client.ts
65
74
  function resolveTransportHeaders(transport) {
@@ -322,6 +331,7 @@ var ar_default = {
322
331
  translate_trigger_fired: "تم التشغيل بواسطة",
323
332
  translate_trigger_payload: "الحمولة",
324
333
  translate_messages_disabled: "يعمل هذا الوكيل من مشغّلاته فقط.",
334
+ translate_read_only: "أنت تطّلع على هذه المحادثة. لا يمكن إرسال شيء من هنا.",
325
335
  translate_access_refused: "لم يقبل هذا الوكيل تسجيل دخولك.",
326
336
  translate_agent_unreachable: "تعذّر الوصول إلى هذا الوكيل الآن. نعيد المحاولة...",
327
337
  translate_agent_missing: "هذا الوكيل غير متاح.",
@@ -476,6 +486,7 @@ var en_default = {
476
486
  translate_trigger_fired: "Triggered by",
477
487
  translate_trigger_payload: "Payload",
478
488
  translate_messages_disabled: "This agent only runs from its triggers.",
489
+ translate_read_only: "You're viewing this conversation. Nothing can be sent from here.",
479
490
  translate_access_refused: "This agent did not accept your sign-in.",
480
491
  translate_agent_unreachable: "This agent cannot be reached right now. Trying again...",
481
492
  translate_agent_missing: "This agent is not available.",
@@ -724,15 +735,12 @@ function toolCallAnimation(part) {
724
735
  rows: state === "complete" ? toolCallRows(part) : undefined
725
736
  };
726
737
  }
727
- function approvalPrompt(part, locale) {
728
- return toolCallMetadataOf(part)?.approval?.[locale] || undefined;
729
- }
730
738
  function approvalCard(part, locale) {
731
739
  const approved = part.approval?.approved;
732
740
  const { state } = toolCallAnimation(part);
733
741
  const decision = approved === undefined ? part.state === "approval-requested" ? "pending" : "cancelled" : !approved ? "declined" : state === "error" ? "failed" : "approved";
734
742
  return {
735
- sentence: approvalPrompt(part, locale) ?? translate(locale, "translate_approval_action", { tool: wordsOf(part.name) }),
743
+ sentence: approvalTitleOf(part.input) ?? translate(locale, "translate_approval_action", { tool: wordsOf(part.name) }),
736
744
  decision,
737
745
  decisionKey: DECISION_KEYS[decision]
738
746
  };
@@ -1524,7 +1532,7 @@ function createCortexSocket(options) {
1524
1532
  resolveTransportHeaders(transport).then((headers) => {
1525
1533
  if (closed)
1526
1534
  return;
1527
- ws = new WebSocket(appendTokenToUrl(wsUrl, headers));
1535
+ ws = new WebSocket(appendAuthToUrl(wsUrl, headers));
1528
1536
  ws.addEventListener("open", () => {
1529
1537
  open = true;
1530
1538
  const opened = ws;
@@ -1583,14 +1591,21 @@ function resolveWsUrl(wsUrl, baseUrl) {
1583
1591
  parsed.pathname = parsed.pathname.replace(/\/$/, "") + "/ws";
1584
1592
  return parsed.toString();
1585
1593
  }
1586
- function appendTokenToUrl(wsUrl, headers) {
1587
- const authHeader = headers["Authorization"] ?? headers["authorization"];
1588
- if (!authHeader?.startsWith("Bearer "))
1594
+ function appendAuthToUrl(wsUrl, headers) {
1595
+ const header = (name) => Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
1596
+ const bearer = header("Authorization");
1597
+ const params = Object.entries({
1598
+ token: bearer?.startsWith("Bearer ") ? bearer.slice(7) : undefined,
1599
+ operator: header(OPERATOR_HEADER),
1600
+ inspect: header(INSPECT_HEADER)
1601
+ }).filter((entry) => Boolean(entry[1]));
1602
+ if (params.length === 0)
1589
1603
  return wsUrl;
1590
1604
  const parsed = new URL(wsUrl, window.location.origin);
1591
- if (parsed.searchParams.has("token"))
1592
- return parsed.toString();
1593
- parsed.searchParams.set("token", authHeader.slice(7));
1605
+ for (const [param, value] of params) {
1606
+ if (!parsed.searchParams.has(param))
1607
+ parsed.searchParams.set(param, value);
1608
+ }
1594
1609
  return parsed.toString();
1595
1610
  }
1596
1611
  // src/internal/client/src/chat/ask-until-answered.ts
@@ -1811,7 +1826,7 @@ function splitTail(text, consumed, runs) {
1811
1826
  if (end <= 0)
1812
1827
  break;
1813
1828
  let take = Math.min(end, run.length - consumed);
1814
- if (take < end && /[\uDC00-\uDFFF]/.test(text[end - take]))
1829
+ if (take < end && /[\uDC00-\uDFFF]/.test(text.charAt(end - take)))
1815
1830
  take += 1;
1816
1831
  const span = document.createElement("span");
1817
1832
  span.className = "cortex-stream-fade";
@@ -4557,17 +4572,19 @@ var ChatHeaderComponent = class ChatHeaderComponent2 {
4557
4572
  template: `@let header = context();
4558
4573
 
4559
4574
  <ng-template #newChatButton>
4560
- <button
4561
- type="button"
4562
- (click)="header.newChat()"
4563
- [attr.aria-label]="'translate_new_chat' | translate"
4564
- [title]="'translate_new_chat' | translate"
4565
- class="cortex-widget__header-btn cortex-widget__new-btn"
4566
- >
4567
- <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
4568
- <path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" />
4569
- </svg>
4570
- </button>
4575
+ @if (header.newChat; as newChat) {
4576
+ <button
4577
+ type="button"
4578
+ (click)="newChat()"
4579
+ [attr.aria-label]="'translate_new_chat' | translate"
4580
+ [title]="'translate_new_chat' | translate"
4581
+ class="cortex-widget__header-btn cortex-widget__new-btn"
4582
+ >
4583
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
4584
+ <path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" />
4585
+ </svg>
4586
+ </button>
4587
+ }
4571
4588
  </ng-template>
4572
4589
 
4573
4590
  @if (header.screen === 'threads') {
@@ -4696,17 +4713,19 @@ i0.ɵɵngDeclareClassMetadata({
4696
4713
  template: `@let header = context();
4697
4714
 
4698
4715
  <ng-template #newChatButton>
4699
- <button
4700
- type="button"
4701
- (click)="header.newChat()"
4702
- [attr.aria-label]="'translate_new_chat' | translate"
4703
- [title]="'translate_new_chat' | translate"
4704
- class="cortex-widget__header-btn cortex-widget__new-btn"
4705
- >
4706
- <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
4707
- <path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" />
4708
- </svg>
4709
- </button>
4716
+ @if (header.newChat; as newChat) {
4717
+ <button
4718
+ type="button"
4719
+ (click)="newChat()"
4720
+ [attr.aria-label]="'translate_new_chat' | translate"
4721
+ [title]="'translate_new_chat' | translate"
4722
+ class="cortex-widget__header-btn cortex-widget__new-btn"
4723
+ >
4724
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
4725
+ <path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" />
4726
+ </svg>
4727
+ </button>
4728
+ }
4710
4729
  </ng-template>
4711
4730
 
4712
4731
  @if (header.screen === 'threads') {
@@ -9689,9 +9708,10 @@ var MessageToolCallAnimatedComponent = class MessageToolCallAnimatedComponent2 {
9689
9708
  } : null;
9690
9709
  }, ...ngDevMode ? [{ debugName: "mountedComponent" }] : []);
9691
9710
  clientToolFallback = signal(false, ...ngDevMode ? [{ debugName: "clientToolFallback" }] : []);
9692
- owed = computed(() => this.toolCallPart().state === "input-complete" && !this.chatService.isAgentWorking(), ...ngDevMode ? [{ debugName: "owed" }] : []);
9711
+ answers = computed(() => this.chatService.capabilities()?.messages === true, ...ngDevMode ? [{ debugName: "answers" }] : []);
9712
+ owed = computed(() => this.answers() && this.toolCallPart().state === "input-complete" && !this.chatService.isAgentWorking(), ...ngDevMode ? [{ debugName: "owed" }] : []);
9693
9713
  showClientTool = computed(() => !this.clientToolFallback() && this.owed() && toolCallMetadataOf(this.toolCallPart())?.embed !== undefined, ...ngDevMode ? [{ debugName: "showClientTool" }] : []);
9694
- approvalRequested = computed(() => this.toolCallPart().state === "approval-requested" && !this.chatService.isAgentWorking(), ...ngDevMode ? [{ debugName: "approvalRequested" }] : []);
9714
+ approvalRequested = computed(() => this.answers() && this.toolCallPart().state === "approval-requested" && !this.chatService.isAgentWorking(), ...ngDevMode ? [{ debugName: "approvalRequested" }] : []);
9695
9715
  animation = computed(() => toolCallAnimation(this.toolCallPart()), ...ngDevMode ? [{ debugName: "animation" }] : []);
9696
9716
  animState = computed(() => this.animation().state, ...ngDevMode ? [{ debugName: "animState" }] : []);
9697
9717
  gated = computed(() => this.animation().gated, ...ngDevMode ? [{ debugName: "gated" }] : []);
@@ -11621,7 +11641,7 @@ var MessageListComponent = class MessageListComponent2 {
11621
11641
  <cortex-message-aborted-flag [label]="failureLabel(failure)" />
11622
11642
  <!-- The reason may name the model provider's answer: shown only where
11623
11643
  the debug view is offered. -->
11624
- @if (config().showDebugButton && failure.reason) {
11644
+ @if (chatService.capabilities()?.debug && failure.reason) {
11625
11645
  <p class="cortex-aborted-flag__reason">{{ failure.reason }}</p>
11626
11646
  }
11627
11647
  }
@@ -11717,7 +11737,7 @@ i0.ɵɵngDeclareClassMetadata({
11717
11737
  <cortex-message-aborted-flag [label]="failureLabel(failure)" />
11718
11738
  <!-- The reason may name the model provider's answer: shown only where
11719
11739
  the debug view is offered. -->
11720
- @if (config().showDebugButton && failure.reason) {
11740
+ @if (chatService.capabilities()?.debug && failure.reason) {
11721
11741
  <p class="cortex-aborted-flag__reason">{{ failure.reason }}</p>
11722
11742
  }
11723
11743
  }
@@ -11944,22 +11964,24 @@ var ThreadListComponent = class ThreadListComponent2 {
11944
11964
  }
11945
11965
  </button>
11946
11966
 
11947
- <button
11948
- type="button"
11949
- [attr.aria-label]="'translate_delete_conversation' | translate"
11950
- (click)="list.deleteThread(thread.id)"
11951
- class="cortex-widget__thread-delete"
11952
- [class.cortex-widget__thread-delete--active]="isActive"
11953
- >
11954
- <svg width="12" height="12" viewBox="0 0 16 16" fill="none">
11955
- <path
11956
- d="M4 4l8 8M12 4l-8 8"
11957
- stroke="currentColor"
11958
- stroke-width="1.3"
11959
- stroke-linecap="round"
11960
- />
11961
- </svg>
11962
- </button>
11967
+ @if (list.deleteThread; as deleteThread) {
11968
+ <button
11969
+ type="button"
11970
+ [attr.aria-label]="'translate_delete_conversation' | translate"
11971
+ (click)="deleteThread(thread.id)"
11972
+ class="cortex-widget__thread-delete"
11973
+ [class.cortex-widget__thread-delete--active]="isActive"
11974
+ >
11975
+ <svg width="12" height="12" viewBox="0 0 16 16" fill="none">
11976
+ <path
11977
+ d="M4 4l8 8M12 4l-8 8"
11978
+ stroke="currentColor"
11979
+ stroke-width="1.3"
11980
+ stroke-linecap="round"
11981
+ />
11982
+ </svg>
11983
+ </button>
11984
+ }
11963
11985
 
11964
11986
  <svg
11965
11987
  class="cortex-widget__thread-arrow"
@@ -12005,12 +12027,14 @@ var ThreadListComponent = class ThreadListComponent2 {
12005
12027
  <p class="cortex-widget__threads-empty-subtitle">
12006
12028
  {{ 'translate_start_a_new_conversation' | translate }}
12007
12029
  </p>
12008
- <button
12009
- (click)="list.newChat()"
12010
- class="cortex-widget__new-chat-btn cortex-widget__new-chat-btn--empty-state"
12011
- >
12012
- {{ 'translate_new_chat' | translate }}
12013
- </button>
12030
+ @if (list.newChat; as newChat) {
12031
+ <button
12032
+ (click)="newChat()"
12033
+ class="cortex-widget__new-chat-btn cortex-widget__new-chat-btn--empty-state"
12034
+ >
12035
+ {{ 'translate_new_chat' | translate }}
12036
+ </button>
12037
+ }
12014
12038
  </div>
12015
12039
  }
12016
12040
  `,
@@ -12100,22 +12124,24 @@ i0.ɵɵngDeclareClassMetadata({
12100
12124
  }
12101
12125
  </button>
12102
12126
 
12103
- <button
12104
- type="button"
12105
- [attr.aria-label]="'translate_delete_conversation' | translate"
12106
- (click)="list.deleteThread(thread.id)"
12107
- class="cortex-widget__thread-delete"
12108
- [class.cortex-widget__thread-delete--active]="isActive"
12109
- >
12110
- <svg width="12" height="12" viewBox="0 0 16 16" fill="none">
12111
- <path
12112
- d="M4 4l8 8M12 4l-8 8"
12113
- stroke="currentColor"
12114
- stroke-width="1.3"
12115
- stroke-linecap="round"
12116
- />
12117
- </svg>
12118
- </button>
12127
+ @if (list.deleteThread; as deleteThread) {
12128
+ <button
12129
+ type="button"
12130
+ [attr.aria-label]="'translate_delete_conversation' | translate"
12131
+ (click)="deleteThread(thread.id)"
12132
+ class="cortex-widget__thread-delete"
12133
+ [class.cortex-widget__thread-delete--active]="isActive"
12134
+ >
12135
+ <svg width="12" height="12" viewBox="0 0 16 16" fill="none">
12136
+ <path
12137
+ d="M4 4l8 8M12 4l-8 8"
12138
+ stroke="currentColor"
12139
+ stroke-width="1.3"
12140
+ stroke-linecap="round"
12141
+ />
12142
+ </svg>
12143
+ </button>
12144
+ }
12119
12145
 
12120
12146
  <svg
12121
12147
  class="cortex-widget__thread-arrow"
@@ -12161,12 +12187,14 @@ i0.ɵɵngDeclareClassMetadata({
12161
12187
  <p class="cortex-widget__threads-empty-subtitle">
12162
12188
  {{ 'translate_start_a_new_conversation' | translate }}
12163
12189
  </p>
12164
- <button
12165
- (click)="list.newChat()"
12166
- class="cortex-widget__new-chat-btn cortex-widget__new-chat-btn--empty-state"
12167
- >
12168
- {{ 'translate_new_chat' | translate }}
12169
- </button>
12190
+ @if (list.newChat; as newChat) {
12191
+ <button
12192
+ (click)="newChat()"
12193
+ class="cortex-widget__new-chat-btn cortex-widget__new-chat-btn--empty-state"
12194
+ >
12195
+ {{ 'translate_new_chat' | translate }}
12196
+ </button>
12197
+ }
12170
12198
  </div>
12171
12199
  }
12172
12200
  `
@@ -12238,6 +12266,7 @@ i0.ɵɵngDeclareClassMetadata({
12238
12266
  var CortexChatWidgetComponent = class CortexChatWidgetComponent2 {
12239
12267
  config = input.required(...ngDevMode ? [{ debugName: "config" }] : []);
12240
12268
  debugMode = signal(false, ...ngDevMode ? [{ debugName: "debugMode" }] : []);
12269
+ writes = computed(() => this.chatService.capabilities()?.readOnly === false, ...ngDevMode ? [{ debugName: "writes" }] : []);
12241
12270
  screen = signal("chat", ...ngDevMode ? [{ debugName: "screen" }] : []);
12242
12271
  sidebarOpen = signal(false, ...ngDevMode ? [{ debugName: "sidebarOpen" }] : []);
12243
12272
  viewMode = computed(() => this.config().viewMode ?? "helper", ...ngDevMode ? [{ debugName: "viewMode" }] : []);
@@ -12265,8 +12294,10 @@ var CortexChatWidgetComponent = class CortexChatWidgetComponent2 {
12265
12294
  threads: this.threadService.threads(),
12266
12295
  selectedThreadId: this.chatService.selectedThread()?.id,
12267
12296
  openThread: (thread) => this.selectThread(thread),
12268
- deleteThread: (threadId) => this.chatService.deleteThread(threadId).subscribe(),
12269
- newChat: () => this.newChat()
12297
+ ...this.writes() && {
12298
+ deleteThread: (threadId) => this.chatService.deleteThread(threadId).subscribe(),
12299
+ newChat: () => this.newChat()
12300
+ }
12270
12301
  }), ...ngDevMode ? [{ debugName: "threadList" }] : []);
12271
12302
  chatService;
12272
12303
  queue;
@@ -12286,6 +12317,15 @@ var CortexChatWidgetComponent = class CortexChatWidgetComponent2 {
12286
12317
  this.queue.store.clear(selectedThreadId);
12287
12318
  selectedThreadId = nextThreadId;
12288
12319
  }, { injector: this.injector });
12320
+ let openedConfigThread = false;
12321
+ effect(() => {
12322
+ const id = this.config().thread;
12323
+ const named = id && this.threadService.threads()?.find((thread) => thread.id === id);
12324
+ if (openedConfigThread || !named)
12325
+ return;
12326
+ openedConfigThread = true;
12327
+ untracked(() => this.selectThread(named));
12328
+ }, { injector: this.injector });
12289
12329
  this.chatService.events$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
12290
12330
  if (event === "onError") {
12291
12331
  this.queue.store.restoreConsumed(this.chatService.selectedThread()?.id);
@@ -12308,10 +12348,10 @@ var CortexChatWidgetComponent = class CortexChatWidgetComponent2 {
12308
12348
  threadCount: this.threadService.threads()?.length,
12309
12349
  isWorking: this.chatService.isAgentWorking(),
12310
12350
  debug: {
12311
- shown: Boolean(this.config().showDebugButton),
12351
+ shown: Boolean(this.chatService.capabilities()?.debug),
12312
12352
  on: this.debugMode()
12313
12353
  },
12314
- newChat: () => this.newChat(),
12354
+ ...this.writes() && { newChat: () => this.newChat() },
12315
12355
  openHistory: () => this.openHistory(),
12316
12356
  closeHistory: () => this.closeHistory(),
12317
12357
  toggleDebug: () => this.toggleDebugMode()
@@ -12502,7 +12542,12 @@ var CortexChatWidgetComponent = class CortexChatWidgetComponent2 {
12502
12542
  </p>
12503
12543
  } @else if (chatService.capabilities()?.messages === false) {
12504
12544
  <p class="cortex-widget__messages-disabled">
12505
- {{ 'translate_messages_disabled' | translate }}
12545
+ {{
12546
+ (chatService.capabilities()?.readOnly
12547
+ ? 'translate_read_only'
12548
+ : 'translate_messages_disabled'
12549
+ ) | translate
12550
+ }}
12506
12551
  </p>
12507
12552
  } @else if (chatService.capabilities() !== undefined) {
12508
12553
  <cortex-chat-composer />
@@ -12703,7 +12748,12 @@ i0.ɵɵngDeclareClassMetadata({
12703
12748
  </p>
12704
12749
  } @else if (chatService.capabilities()?.messages === false) {
12705
12750
  <p class="cortex-widget__messages-disabled">
12706
- {{ 'translate_messages_disabled' | translate }}
12751
+ {{
12752
+ (chatService.capabilities()?.readOnly
12753
+ ? 'translate_read_only'
12754
+ : 'translate_messages_disabled'
12755
+ ) | translate
12756
+ }}
12707
12757
  </p>
12708
12758
  } @else if (chatService.capabilities() !== undefined) {
12709
12759
  <cortex-chat-composer />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m6d/cortex-angular",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Angular chat widget for Cortex agents",
5
5
  "license": "MIT",
6
6
  "sideEffects": false,
@@ -61,12 +61,23 @@ export type CreateThreadRequest = {
61
61
  */
62
62
  export type AgentCapabilities = {
63
63
  attachments: boolean;
64
- /** False when the agent's `message` trigger is off: the widget hides its composer. */
64
+ /**
65
+ * False when this caller may not send to the chat: the agent's `message`
66
+ * trigger is off, or the caller only reads (`readOnly`). The widget hides
67
+ * its composer and answers no tool call.
68
+ */
65
69
  messages: boolean;
70
+ /** True when the caller only reads, as an operator inspecting a user's thread does. */
71
+ readOnly: boolean;
66
72
  /** Prompt chips on the blank-chat landing per locale; clicking one sends its `prompt` as the first message. */
67
73
  suggestions: Suggestions;
68
74
  /** Headline of the blank-chat landing per locale; "" falls back to the widget's built-in copy. */
69
75
  greeting: Greeting;
76
+ /**
77
+ * Whether this caller gets the debug view: tool inputs and outputs, reasoning,
78
+ * model calls. Cortex sends anyone else only what the widget's normal UI draws.
79
+ */
80
+ debug: boolean;
70
81
  };
71
82
  /**
72
83
  * One landing-page chip: the widget shows `title` and sends `prompt` as the
@@ -165,7 +176,11 @@ export type CortexConfig = {
165
176
  * or a map keyed by locale (`{ en: 'Support', ar: 'الدعم' }`).
166
177
  */
167
178
  agentName?: Localized<string>;
168
- showDebugButton?: boolean;
179
+ /**
180
+ * A thread to open when the widget mounts, by id, once the thread list holds
181
+ * it: a deep link into one conversation, such as the console's inspector.
182
+ */
183
+ thread?: string;
169
184
  /**
170
185
  * Render embedded tool pages served from this page's own origin with their
171
186
  * full identity — which, same-origin, means no effective sandbox at all: such
@@ -193,12 +208,13 @@ export type CortexHeaderContext = {
193
208
  /** How many conversations the history lists; undefined while the first load is out. */
194
209
  threadCount: number | undefined;
195
210
  isWorking: boolean;
196
- /** Whether the consumer enabled the debug switch, and whether it is on. */
211
+ /** Whether Cortex offers this caller the debug view, and whether it is on. */
197
212
  debug: {
198
213
  shown: boolean;
199
214
  on: boolean;
200
215
  };
201
- newChat: () => void;
216
+ /** Absent until the handshake says the caller may write; an operator inspecting a thread only reads. */
217
+ newChat?: () => void;
202
218
  /** Shows the history: the history screen, or in full mode the sidebar where it is hidden. */
203
219
  openHistory: () => void;
204
220
  /** Returns to the open chat from wherever `openHistory` went. */
@@ -236,8 +252,9 @@ export type CortexThreadListContext = {
236
252
  /** The open thread's id; undefined on a new chat. */
237
253
  selectedThreadId: string | undefined;
238
254
  openThread: (thread: ThreadSummary) => void;
239
- deleteThread: (threadId: string) => void;
240
- newChat: () => void;
255
+ /** Both absent until the handshake says the caller may write; an operator inspecting a thread only reads. */
256
+ deleteThread?: (threadId: string) => void;
257
+ newChat?: () => void;
241
258
  };
242
259
  /** Each part of the widget a consumer can draw itself, with the context it receives. */
243
260
  export type CortexSlotContexts<View> = {
@@ -442,6 +459,12 @@ declare class CortexChatService {
442
459
  export declare class CortexChatWidgetComponent implements OnInit {
443
460
  readonly config: import("@angular/core").InputSignal<CortexAngularConfig>;
444
461
  protected readonly debugMode: import("@angular/core").WritableSignal<boolean>;
462
+ /**
463
+ * Whether this caller may start and delete conversations: only once the
464
+ * handshake says so, like every capability-gated control, and never for a
465
+ * caller that only reads, as an operator inspecting a thread does.
466
+ */
467
+ private readonly writes;
445
468
  protected readonly screen: import("@angular/core").WritableSignal<"chat" | "threads">;
446
469
  protected readonly sidebarOpen: import("@angular/core").WritableSignal<boolean>;
447
470
  protected readonly viewMode: import("@angular/core").Signal<CortexViewMode>;
@@ -455,6 +478,10 @@ export declare class CortexChatWidgetComponent implements OnInit {
455
478
  /** Why the handshake has not answered, worded for the composer's place. */
456
479
  protected readonly unansweredNotice: import("@angular/core").Signal<"translate_access_refused" | "translate_agent_unreachable" | "translate_agent_missing" | undefined>;
457
480
  protected readonly chatHeader: import("@angular/core").Signal<{
481
+ openHistory: () => void;
482
+ closeHistory: () => void;
483
+ toggleDebug: () => void;
484
+ newChat?: (() => void) | undefined;
458
485
  screen: "chat" | "threads";
459
486
  viewMode: CortexViewMode;
460
487
  title: string | null;
@@ -465,12 +492,12 @@ export declare class CortexChatWidgetComponent implements OnInit {
465
492
  shown: boolean;
466
493
  on: boolean;
467
494
  };
468
- newChat: () => void;
495
+ }>;
496
+ protected readonly threadsHeader: import("@angular/core").Signal<{
469
497
  openHistory: () => void;
470
498
  closeHistory: () => void;
471
499
  toggleDebug: () => void;
472
- }>;
473
- protected readonly threadsHeader: import("@angular/core").Signal<{
500
+ newChat?: (() => void) | undefined;
474
501
  screen: "chat" | "threads";
475
502
  viewMode: CortexViewMode;
476
503
  title: string | null;
@@ -481,10 +508,6 @@ export declare class CortexChatWidgetComponent implements OnInit {
481
508
  shown: boolean;
482
509
  on: boolean;
483
510
  };
484
- newChat: () => void;
485
- openHistory: () => void;
486
- closeHistory: () => void;
487
- toggleDebug: () => void;
488
511
  }>;
489
512
  protected readonly landing: import("@angular/core").Signal<{
490
513
  threads: ThreadSummary[] | undefined;
@@ -496,11 +519,11 @@ export declare class CortexChatWidgetComponent implements OnInit {
496
519
  send: (prompt: string) => void;
497
520
  }>;
498
521
  protected readonly threadList: import("@angular/core").Signal<{
522
+ deleteThread?: ((threadId: string) => import("rxjs").Subscription) | undefined;
523
+ newChat?: (() => void) | undefined;
499
524
  threads: ThreadSummary[] | undefined;
500
525
  selectedThreadId: string | undefined;
501
526
  openThread: (thread: ThreadSummary) => void;
502
- deleteThread: (threadId: string) => import("rxjs").Subscription;
503
- newChat: () => void;
504
527
  }>;
505
528
  protected chatService: CortexChatService;
506
529
  protected queue: AttachmentQueueService;