kanna-code 0.26.6 → 0.26.7

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.
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" type="image/png" href="/favicon.png" />
7
7
  <title>Kanna</title>
8
- <script type="module" crossorigin src="/assets/index-CcdP0kaK.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-CH3ZbXse.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-C5RBxQW4.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kanna-code",
3
3
  "type": "module",
4
- "version": "0.26.6",
4
+ "version": "0.26.7",
5
5
  "description": "A beautiful web UI for Claude Code",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -63,6 +63,8 @@ interface ActiveTurn {
63
63
  hasFinalResult: boolean
64
64
  cancelRequested: boolean
65
65
  cancelRecorded: boolean
66
+ clientTraceId?: string
67
+ profilingStartedAt?: number
66
68
  }
67
69
 
68
70
  interface ClaudeSessionHandle {
@@ -102,6 +104,11 @@ interface AgentCoordinatorArgs {
102
104
  }) => Promise<ClaudeSessionHandle>
103
105
  }
104
106
 
107
+ interface SendToStartingProfile {
108
+ traceId: string
109
+ startedAt: number
110
+ }
111
+
105
112
  function timestamped<T extends Omit<TranscriptEntry, "_id" | "createdAt">>(
106
113
  entry: T,
107
114
  createdAt = Date.now()
@@ -138,6 +145,31 @@ function escapeXmlAttribute(value: string) {
138
145
  .replaceAll(">", "&gt;")
139
146
  }
140
147
 
148
+ function isSendToStartingProfilingEnabled() {
149
+ return process.env.KANNA_PROFILE_SEND_TO_STARTING === "1"
150
+ }
151
+
152
+ function elapsedProfileMs(startedAt: number) {
153
+ return Number((performance.now() - startedAt).toFixed(1))
154
+ }
155
+
156
+ function logSendToStartingProfile(
157
+ profile: SendToStartingProfile | null | undefined,
158
+ stage: string,
159
+ details?: Record<string, unknown>
160
+ ) {
161
+ if (!profile || !isSendToStartingProfilingEnabled()) {
162
+ return
163
+ }
164
+
165
+ console.log("[kanna/send->starting][server]", JSON.stringify({
166
+ traceId: profile.traceId,
167
+ stage,
168
+ elapsedMs: elapsedProfileMs(profile.startedAt),
169
+ ...details,
170
+ }))
171
+ }
172
+
141
173
  export function buildAttachmentHintText(attachments: ChatAttachment[]) {
142
174
  if (attachments.length === 0) return ""
143
175
 
@@ -636,6 +668,18 @@ export class AgentCoordinator {
636
668
  return new Set(this.drainingStreams.keys())
637
669
  }
638
670
 
671
+ getActiveTurnProfile(chatId: string): SendToStartingProfile | null {
672
+ const active = this.activeTurns.get(chatId)
673
+ if (!active?.clientTraceId || active.profilingStartedAt === undefined) {
674
+ return null
675
+ }
676
+
677
+ return {
678
+ traceId: active.clientTraceId,
679
+ startedAt: active.profilingStartedAt,
680
+ }
681
+ }
682
+
639
683
  async stopDraining(chatId: string) {
640
684
  const draining = this.drainingStreams.get(chatId)
641
685
  if (!draining) return
@@ -691,7 +735,15 @@ export class AgentCoordinator {
691
735
  serviceTier?: "fast"
692
736
  planMode: boolean
693
737
  appendUserPrompt: boolean
738
+ profile?: SendToStartingProfile | null
694
739
  }) {
740
+ logSendToStartingProfile(args.profile, "start_turn.begin", {
741
+ chatId: args.chatId,
742
+ provider: args.provider,
743
+ appendUserPrompt: args.appendUserPrompt,
744
+ planMode: args.planMode,
745
+ })
746
+
695
747
  // Close any lingering draining stream before starting a new turn.
696
748
  const draining = this.drainingStreams.get(args.chatId)
697
749
  if (draining) {
@@ -706,8 +758,16 @@ export class AgentCoordinator {
706
758
 
707
759
  if (!chat.provider) {
708
760
  await this.store.setChatProvider(args.chatId, args.provider)
761
+ logSendToStartingProfile(args.profile, "start_turn.provider_set", {
762
+ chatId: args.chatId,
763
+ provider: args.provider,
764
+ })
709
765
  }
710
766
  await this.store.setPlanMode(args.chatId, args.planMode)
767
+ logSendToStartingProfile(args.profile, "start_turn.plan_mode_set", {
768
+ chatId: args.chatId,
769
+ planMode: args.planMode,
770
+ })
711
771
 
712
772
  const existingMessages = this.store.getMessages(args.chatId)
713
773
  const shouldGenerateTitle = args.appendUserPrompt && chat.title === "New Chat" && existingMessages.length === 0
@@ -715,6 +775,10 @@ export class AgentCoordinator {
715
775
 
716
776
  if (optimisticTitle) {
717
777
  await this.store.renameChat(args.chatId, optimisticTitle)
778
+ logSendToStartingProfile(args.profile, "start_turn.optimistic_title_set", {
779
+ chatId: args.chatId,
780
+ title: optimisticTitle,
781
+ })
718
782
  }
719
783
 
720
784
  const project = this.store.getProject(chat.projectId)
@@ -728,8 +792,15 @@ export class AgentCoordinator {
728
792
  Date.now()
729
793
  )
730
794
  await this.store.appendMessage(args.chatId, userPromptEntry)
795
+ logSendToStartingProfile(args.profile, "start_turn.user_prompt_appended", {
796
+ chatId: args.chatId,
797
+ entryId: userPromptEntry._id,
798
+ })
731
799
  }
732
800
  await this.store.recordTurnStarted(args.chatId)
801
+ logSendToStartingProfile(args.profile, "start_turn.turn_started_recorded", {
802
+ chatId: args.chatId,
803
+ })
733
804
 
734
805
  if (shouldGenerateTitle) {
735
806
  void this.generateTitleInBackground(args.chatId, args.content, project.localPath, optimisticTitle ?? "New Chat")
@@ -755,6 +826,11 @@ export class AgentCoordinator {
755
826
 
756
827
  let turn: HarnessTurn
757
828
  if (args.provider === "claude") {
829
+ logSendToStartingProfile(args.profile, "start_turn.provider_boot.begin", {
830
+ chatId: args.chatId,
831
+ provider: args.provider,
832
+ model: args.model,
833
+ })
758
834
  turn = await this.startClaudeTurn({
759
835
  chatId: args.chatId,
760
836
  localPath: project.localPath,
@@ -764,7 +840,17 @@ export class AgentCoordinator {
764
840
  sessionToken: chat.sessionToken,
765
841
  onToolRequest,
766
842
  })
843
+ logSendToStartingProfile(args.profile, "start_turn.provider_boot.ready", {
844
+ chatId: args.chatId,
845
+ provider: args.provider,
846
+ model: args.model,
847
+ })
767
848
  } else {
849
+ logSendToStartingProfile(args.profile, "start_turn.provider_boot.begin", {
850
+ chatId: args.chatId,
851
+ provider: args.provider,
852
+ model: args.model,
853
+ })
768
854
  await this.codexManager.startSession({
769
855
  chatId: args.chatId,
770
856
  cwd: project.localPath,
@@ -772,6 +858,11 @@ export class AgentCoordinator {
772
858
  serviceTier: args.serviceTier,
773
859
  sessionToken: chat.sessionToken,
774
860
  })
861
+ logSendToStartingProfile(args.profile, "start_turn.session_ready", {
862
+ chatId: args.chatId,
863
+ provider: args.provider,
864
+ model: args.model,
865
+ })
775
866
  turn = await this.codexManager.startTurn({
776
867
  chatId: args.chatId,
777
868
  content: buildPromptText(args.content, args.attachments),
@@ -781,6 +872,11 @@ export class AgentCoordinator {
781
872
  planMode: args.planMode,
782
873
  onToolRequest,
783
874
  })
875
+ logSendToStartingProfile(args.profile, "start_turn.provider_boot.ready", {
876
+ chatId: args.chatId,
877
+ provider: args.provider,
878
+ model: args.model,
879
+ })
784
880
  }
785
881
 
786
882
  const active: ActiveTurn = {
@@ -797,9 +893,19 @@ export class AgentCoordinator {
797
893
  hasFinalResult: false,
798
894
  cancelRequested: false,
799
895
  cancelRecorded: false,
896
+ clientTraceId: args.profile?.traceId,
897
+ profilingStartedAt: args.profile?.startedAt,
800
898
  }
801
899
  this.activeTurns.set(args.chatId, active)
900
+ logSendToStartingProfile(args.profile, "start_turn.active_turn_registered", {
901
+ chatId: args.chatId,
902
+ status: active.status,
903
+ })
802
904
  this.onStateChange()
905
+ logSendToStartingProfile(args.profile, "start_turn.state_change_emitted", {
906
+ chatId: args.chatId,
907
+ status: active.status,
908
+ })
803
909
 
804
910
  if (turn.getAccountInfo) {
805
911
  void turn.getAccountInfo()
@@ -826,6 +932,9 @@ export class AgentCoordinator {
826
932
  throw new Error("Claude session was not initialized")
827
933
  }
828
934
  await session.session.sendPrompt(buildPromptText(args.content, args.attachments))
935
+ logSendToStartingProfile(args.profile, "start_turn.claude_prompt_sent", {
936
+ chatId: args.chatId,
937
+ })
829
938
  return
830
939
  }
831
940
 
@@ -893,14 +1002,26 @@ export class AgentCoordinator {
893
1002
  }
894
1003
 
895
1004
  async send(command: Extract<ClientCommand, { type: "chat.send" }>) {
1005
+ const profile = command.clientTraceId
1006
+ ? { traceId: command.clientTraceId, startedAt: performance.now() }
1007
+ : null
896
1008
  let chatId = command.chatId
897
1009
 
1010
+ logSendToStartingProfile(profile, "chat_send.received", {
1011
+ existingChatId: command.chatId ?? null,
1012
+ projectId: command.projectId ?? null,
1013
+ })
1014
+
898
1015
  if (!chatId) {
899
1016
  if (!command.projectId) {
900
1017
  throw new Error("Missing projectId for new chat")
901
1018
  }
902
1019
  const created = await this.store.createChat(command.projectId)
903
1020
  chatId = created.id
1021
+ logSendToStartingProfile(profile, "chat_send.chat_created", {
1022
+ chatId,
1023
+ projectId: command.projectId,
1024
+ })
904
1025
  }
905
1026
 
906
1027
  const chat = this.store.requireChat(chatId)
@@ -916,6 +1037,13 @@ export class AgentCoordinator {
916
1037
  serviceTier: settings.serviceTier,
917
1038
  planMode: settings.planMode,
918
1039
  appendUserPrompt: true,
1040
+ profile,
1041
+ })
1042
+
1043
+ logSendToStartingProfile(profile, "chat_send.ready_for_ack", {
1044
+ chatId,
1045
+ provider,
1046
+ model: settings.model,
919
1047
  })
920
1048
 
921
1049
  return { chatId }
@@ -15,6 +15,28 @@ import { deriveChatSnapshot, deriveLocalProjectsSnapshot, deriveSidebarData } fr
15
15
 
16
16
  const DEFAULT_CHAT_RECENT_LIMIT = 200
17
17
 
18
+ function isSendToStartingProfilingEnabled() {
19
+ return process.env.KANNA_PROFILE_SEND_TO_STARTING === "1"
20
+ }
21
+
22
+ function logSendToStartingProfile(
23
+ traceId: string | null | undefined,
24
+ startedAt: number | null | undefined,
25
+ stage: string,
26
+ details?: Record<string, unknown>
27
+ ) {
28
+ if (!traceId || startedAt === undefined || startedAt === null || !isSendToStartingProfilingEnabled()) {
29
+ return
30
+ }
31
+
32
+ console.log("[kanna/send->starting][server]", JSON.stringify({
33
+ traceId,
34
+ stage,
35
+ elapsedMs: Number((performance.now() - startedAt).toFixed(1)),
36
+ ...details,
37
+ }))
38
+ }
39
+
18
40
  export interface ClientState {
19
41
  subscriptions: Map<string, SubscriptionTopic>
20
42
  snapshotSignatures: Map<string, string>
@@ -225,6 +247,14 @@ export function createWsRouter({
225
247
  continue
226
248
  }
227
249
  snapshotSignatures.set(id, signature)
250
+ if (topic.type === "chat" && envelope.snapshot.type === "chat" && envelope.snapshot.data?.runtime.status === "starting") {
251
+ const profile = agent.getActiveTurnProfile(topic.chatId)
252
+ logSendToStartingProfile(profile?.traceId, profile?.startedAt, "ws.snapshot_sent", {
253
+ chatId: topic.chatId,
254
+ status: envelope.snapshot.data.runtime.status,
255
+ messageCount: envelope.snapshot.data.messages.length,
256
+ })
257
+ }
228
258
  send(ws, envelope)
229
259
  }
230
260
  }
@@ -437,6 +467,12 @@ export function createWsRouter({
437
467
  }
438
468
  case "chat.send": {
439
469
  const result = await agent.send(command)
470
+ const profile = command.clientTraceId && result.chatId
471
+ ? agent.getActiveTurnProfile(result.chatId)
472
+ : null
473
+ logSendToStartingProfile(profile?.traceId ?? command.clientTraceId, profile?.startedAt, "ws.chat_send_ack", {
474
+ chatId: result.chatId ?? null,
475
+ })
440
476
  send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
441
477
  break
442
478
  }
@@ -73,6 +73,7 @@ export type ClientCommand =
73
73
  type: "chat.send"
74
74
  chatId?: string
75
75
  projectId?: string
76
+ clientTraceId?: string
76
77
  provider?: AgentProvider
77
78
  content: string
78
79
  attachments?: ChatAttachment[]