@cuongtran001/kanna 0.77.2 → 0.78.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.
Files changed (57) hide show
  1. package/dist/client/assets/{arc-B4o-xHwY.js → arc-HbrGgtWf.js} +1 -1
  2. package/dist/client/assets/{architectureDiagram-3BPJPVTR-BmbSl1qx.js → architectureDiagram-3BPJPVTR-B6BrZkV3.js} +1 -1
  3. package/dist/client/assets/{blockDiagram-GPEHLZMM-Bim40iWf.js → blockDiagram-GPEHLZMM-SADca-sV.js} +1 -1
  4. package/dist/client/assets/{c4Diagram-AAUBKEIU-DIC1Vt4P.js → c4Diagram-AAUBKEIU-DsmOdUGS.js} +1 -1
  5. package/dist/client/assets/channel-D7WmNVPH.js +1 -0
  6. package/dist/client/assets/{chunk-2J33WTMH-D8N21cnl.js → chunk-2J33WTMH-knmvy7Jo.js} +1 -1
  7. package/dist/client/assets/{chunk-4BX2VUAB-KAz6Kv2w.js → chunk-4BX2VUAB-BXQX5PhH.js} +1 -1
  8. package/dist/client/assets/{chunk-55IACEB6-DYFOLYdT.js → chunk-55IACEB6-DL2RIc5G.js} +1 -1
  9. package/dist/client/assets/{chunk-727SXJPM-Ig1oTofA.js → chunk-727SXJPM-t5s-omGD.js} +1 -1
  10. package/dist/client/assets/{chunk-AQP2D5EJ-N4H6IIEE.js → chunk-AQP2D5EJ-NrOhyI4c.js} +1 -1
  11. package/dist/client/assets/{chunk-FMBD7UC4-CFu3ujo5.js → chunk-FMBD7UC4-CKdXTN3b.js} +1 -1
  12. package/dist/client/assets/{chunk-ND2GUHAM-CNSLqk89.js → chunk-ND2GUHAM-B2_DXMvT.js} +1 -1
  13. package/dist/client/assets/{chunk-QZHKN3VN-BC7VWFPg.js → chunk-QZHKN3VN-DxxSgeCo.js} +1 -1
  14. package/dist/client/assets/classDiagram-4FO5ZUOK-CBBAqSGe.js +1 -0
  15. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CBBAqSGe.js +1 -0
  16. package/dist/client/assets/{cose-bilkent-S5V4N54A-CpgoSmpN.js → cose-bilkent-S5V4N54A-KB47ZT0t.js} +1 -1
  17. package/dist/client/assets/{dagre-BM42HDAG-GnTWky7q.js → dagre-BM42HDAG-DLGPPRWu.js} +1 -1
  18. package/dist/client/assets/{diagram-2AECGRRQ-686k1nva.js → diagram-2AECGRRQ-Dyl0rVmo.js} +1 -1
  19. package/dist/client/assets/{diagram-5GNKFQAL-OJ7niaXl.js → diagram-5GNKFQAL-mmXcXMiI.js} +1 -1
  20. package/dist/client/assets/{diagram-KO2AKTUF-CI1EZYwZ.js → diagram-KO2AKTUF-BRYdqh1m.js} +1 -1
  21. package/dist/client/assets/{diagram-LMA3HP47-D-MqMcUW.js → diagram-LMA3HP47-M86lyH_o.js} +1 -1
  22. package/dist/client/assets/{diagram-OG6HWLK6-BG55a1D_.js → diagram-OG6HWLK6-BMOVdOa6.js} +1 -1
  23. package/dist/client/assets/{erDiagram-TEJ5UH35-BuhpXnui.js → erDiagram-TEJ5UH35-CwqKL8L7.js} +1 -1
  24. package/dist/client/assets/{flowDiagram-I6XJVG4X-C_jSH-OO.js → flowDiagram-I6XJVG4X-Bebn6Dds.js} +1 -1
  25. package/dist/client/assets/{ganttDiagram-6RSMTGT7-B6JfxGpp.js → ganttDiagram-6RSMTGT7-BKKO7Ido.js} +1 -1
  26. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-D1RS3Blt.js → gitGraphDiagram-PVQCEYII-Bu1dCl2b.js} +1 -1
  27. package/dist/client/assets/{index-CxlLO5Jy.js → index-C8Y4C4vv.js} +1 -1
  28. package/dist/client/assets/{index-BGDqDObt.css → index-DnqTqRrh.css} +1 -1
  29. package/dist/client/assets/{index-bafMJdKb.js → index-U9Elqosx.js} +217 -217
  30. package/dist/client/assets/{infoDiagram-5YYISTIA-DHmkYEYw.js → infoDiagram-5YYISTIA-BUBdZnc6.js} +1 -1
  31. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-WfaJ0v5C.js → ishikawaDiagram-YF4QCWOH-RtGs6O4d.js} +1 -1
  32. package/dist/client/assets/{journeyDiagram-JHISSGLW-C5clnAp_.js → journeyDiagram-JHISSGLW-CeinSrGu.js} +1 -1
  33. package/dist/client/assets/{kanban-definition-UN3LZRKU-DMP2vj0o.js → kanban-definition-UN3LZRKU-CPfptOUC.js} +1 -1
  34. package/dist/client/assets/{linear-BUo6iI20.js → linear-BF0MduRj.js} +1 -1
  35. package/dist/client/assets/{mermaid.core-DSDWYURW.js → mermaid.core-CBl0kCJf.js} +4 -4
  36. package/dist/client/assets/{mindmap-definition-RKZ34NQL-mdg5fGdZ.js → mindmap-definition-RKZ34NQL-C9X5GhDu.js} +1 -1
  37. package/dist/client/assets/{pieDiagram-4H26LBE5-0jeUYHV_.js → pieDiagram-4H26LBE5-BNobnuFC.js} +1 -1
  38. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-kDSx2RzS.js → quadrantDiagram-W4KKPZXB-DQGDnxWh.js} +1 -1
  39. package/dist/client/assets/{requirementDiagram-4Y6WPE33-D56-12y7.js → requirementDiagram-4Y6WPE33-YeBDMr5U.js} +1 -1
  40. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BolbI3NA.js → sankeyDiagram-5OEKKPKP-dRDpQFRB.js} +1 -1
  41. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BJBzrghH.js → sequenceDiagram-3UESZ5HK-Wi1M_8mC.js} +1 -1
  42. package/dist/client/assets/{stateDiagram-AJRCARHV-NCh9DnHC.js → stateDiagram-AJRCARHV-RuvA1pMh.js} +1 -1
  43. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-tcPoqdaH.js +1 -0
  44. package/dist/client/assets/{timeline-definition-PNZ67QCA-YYvBJ2cO.js → timeline-definition-PNZ67QCA-DpbNa24W.js} +1 -1
  45. package/dist/client/assets/{vennDiagram-CIIHVFJN-CXRKid18.js → vennDiagram-CIIHVFJN-BcncNp5Y.js} +1 -1
  46. package/dist/client/assets/{wardley-L42UT6IY-isavNF4i.js → wardley-L42UT6IY-ZJvC_rpL.js} +1 -1
  47. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DnZ8qkUj.js → wardleyDiagram-YWT4CUSO-CxKQt1w8.js} +1 -1
  48. package/dist/client/assets/{xychartDiagram-2RQKCTM6-I7DFznsb.js → xychartDiagram-2RQKCTM6-CpZjcZKF.js} +1 -1
  49. package/dist/client/index.html +2 -2
  50. package/package.json +1 -1
  51. package/src/server/claude-pty/jsonl-to-event.test.ts +195 -0
  52. package/src/server/claude-pty/jsonl-to-event.ts +44 -0
  53. package/src/shared/types.ts +8 -0
  54. package/dist/client/assets/channel-CyEKiNUN.js +0 -1
  55. package/dist/client/assets/classDiagram-4FO5ZUOK-D8ovOIgK.js +0 -1
  56. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-D8ovOIgK.js +0 -1
  57. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CM8MMINn.js +0 -1
@@ -61,6 +61,16 @@ describe("parseJsonlLine", () => {
61
61
  const rl = events.find((e) => e.type === "rate_limit")
62
62
  expect(rl).toBeUndefined()
63
63
  })
64
+
65
+ test("sidechain (subagent) line → no events", () => {
66
+ const line = JSON.stringify({
67
+ type: "assistant",
68
+ isSidechain: true,
69
+ session_id: "sub-sess",
70
+ message: { role: "assistant", content: [{ type: "text", text: "subagent thinking" }] },
71
+ })
72
+ expect(parseJsonlLine(line)).toEqual([])
73
+ })
64
74
  })
65
75
 
66
76
  describe("createJsonlEventParser", () => {
@@ -238,4 +248,189 @@ describe("createJsonlEventParser", () => {
238
248
  expect(types[0]).toBe("session_token")
239
249
  })
240
250
 
251
+ // A Task subagent writes its own messages into the parent transcript with
252
+ // isSidechain:true. They must never reach the main turn stream: a sidechain
253
+ // `result` (or its TUI `turn_duration` synth) would shift the parent's
254
+ // pending prompt seq and finalize the user turn early (UI flips idle while
255
+ // the main turn is still streaming); a sidechain session_id would clobber
256
+ // the parent chat's claude session token.
257
+ test("sidechain result → no transcript result entry and no session_token", () => {
258
+ const parser = createJsonlEventParser()
259
+ const line = JSON.stringify({
260
+ type: "result",
261
+ isSidechain: true,
262
+ session_id: "sub-sess",
263
+ subtype: "success",
264
+ result: "subagent done",
265
+ isError: false,
266
+ duration_ms: 1000,
267
+ })
268
+ const events = parser.parse(line)
269
+ expect(events).toEqual([])
270
+ })
271
+
272
+ test("sidechain turn_duration → no synthesized result entry", () => {
273
+ const parser = createJsonlEventParser()
274
+ const line = JSON.stringify({
275
+ type: "system",
276
+ subtype: "turn_duration",
277
+ isSidechain: true,
278
+ session_id: "sub-sess",
279
+ durationMs: 1234,
280
+ })
281
+ const events = parser.parse(line)
282
+ const resultEntries = events.filter(
283
+ (e) => e.type === "transcript" && (e.entry as { kind?: string }).kind === "result",
284
+ )
285
+ expect(resultEntries).toEqual([])
286
+ expect(events.find((e) => e.type === "session_token")).toBeUndefined()
287
+ })
288
+
289
+ test("non-sidechain turn_duration still synthesizes a result (regression guard)", () => {
290
+ const parser = createJsonlEventParser()
291
+ const line = JSON.stringify({
292
+ type: "system",
293
+ subtype: "turn_duration",
294
+ session_id: "main-sess",
295
+ durationMs: 1234,
296
+ })
297
+ const events = parser.parse(line)
298
+ const resultEntries = events.filter(
299
+ (e) => e.type === "transcript" && (e.entry as { kind?: string }).kind === "result",
300
+ )
301
+ expect(resultEntries).toHaveLength(1)
302
+ })
303
+
304
+ // Claude Code TUI's background task queue (`enqueuePendingNotification` +
305
+ // `useQueueProcessor`) can auto-spawn a follow-up turn after `end_turn` when
306
+ // a `run_in_background:true` bash exits. The wake injects a synthetic
307
+ // `<task-notification>` user message with `isMeta:true` and runs another
308
+ // model query. Kanna never sent a `chat_send` for this turn, so its
309
+ // `result`/`turn_duration` must NOT consume a queued `pendingPromptSeq`
310
+ // (which would steal a real user turn's seq) and must NOT alter Kanna's
311
+ // turn lifecycle. Drop both the synthetic user line and the wake's final
312
+ // result. Mid-turn `isMeta:true` injections (FileReadTool metadata, token
313
+ // budget continuation) are distinguished by arriving AFTER an assistant
314
+ // message in the same turn and must be left alone.
315
+ describe("background auto-wake filtering", () => {
316
+ function makeMetaUser(content: string): string {
317
+ return JSON.stringify({
318
+ type: "user",
319
+ isMeta: true,
320
+ message: { role: "user", content },
321
+ })
322
+ }
323
+ function makeRealUser(text: string): string {
324
+ return JSON.stringify({
325
+ type: "user",
326
+ message: { role: "user", content: text },
327
+ })
328
+ }
329
+ function makeAssistant(text: string): string {
330
+ return JSON.stringify({
331
+ type: "assistant",
332
+ message: { role: "assistant", content: [{ type: "text", text }] },
333
+ })
334
+ }
335
+ function makeResult(): string {
336
+ return JSON.stringify({
337
+ type: "result",
338
+ subtype: "success",
339
+ isError: false,
340
+ duration_ms: 100,
341
+ result: "",
342
+ })
343
+ }
344
+ function makeTurnDuration(): string {
345
+ return JSON.stringify({
346
+ type: "system",
347
+ subtype: "turn_duration",
348
+ session_id: "main-sess",
349
+ durationMs: 100,
350
+ })
351
+ }
352
+ function resultEntries(events: HarnessEvent[]) {
353
+ return events.filter(
354
+ (e) => e.type === "transcript" && (e.entry as { kind?: string }).kind === "result",
355
+ )
356
+ }
357
+
358
+ test("auto-wake: meta user at turn boundary → drop the synthetic user line", () => {
359
+ const parser = createJsonlEventParser()
360
+ // First a real turn ends, putting parser in between-turns state.
361
+ parser.parse(makeRealUser("hi"))
362
+ parser.parse(makeAssistant("hello"))
363
+ parser.parse(makeResult())
364
+ // Then a synthetic isMeta user arrives — the auto-wake.
365
+ const events = parser.parse(makeMetaUser("<task-notification>bash done</task-notification>"))
366
+ const userEntries = events.filter(
367
+ (e) => e.type === "transcript" && (e.entry as { kind?: string }).kind === "user_prompt",
368
+ )
369
+ expect(userEntries).toEqual([])
370
+ })
371
+
372
+ test("auto-wake: result following meta user at turn boundary is dropped", () => {
373
+ const parser = createJsonlEventParser()
374
+ parser.parse(makeRealUser("hi"))
375
+ parser.parse(makeAssistant("hello"))
376
+ parser.parse(makeResult())
377
+ parser.parse(makeMetaUser("<task-notification>bash done</task-notification>"))
378
+ parser.parse(makeAssistant("acknowledged"))
379
+ const events = parser.parse(makeResult())
380
+ expect(resultEntries(events)).toEqual([])
381
+ })
382
+
383
+ test("auto-wake: turn_duration following meta user at turn boundary is dropped", () => {
384
+ const parser = createJsonlEventParser()
385
+ parser.parse(makeRealUser("hi"))
386
+ parser.parse(makeAssistant("hello"))
387
+ parser.parse(makeTurnDuration())
388
+ parser.parse(makeMetaUser("<task-notification>bash done</task-notification>"))
389
+ parser.parse(makeAssistant("acknowledged"))
390
+ const events = parser.parse(makeTurnDuration())
391
+ expect(resultEntries(events)).toEqual([])
392
+ })
393
+
394
+ test("mid-turn meta user (e.g. FileRead metadata) does NOT drop the next result", () => {
395
+ const parser = createJsonlEventParser()
396
+ parser.parse(makeRealUser("read a file"))
397
+ parser.parse(makeAssistant("calling FileRead"))
398
+ // Mid-turn isMeta injection — appears AFTER assistant.
399
+ parser.parse(makeMetaUser("<file-metadata>...</file-metadata>"))
400
+ parser.parse(makeAssistant("done"))
401
+ const events = parser.parse(makeResult())
402
+ // The real turn-end result must still be emitted.
403
+ expect(resultEntries(events).length).toBeGreaterThan(0)
404
+ })
405
+
406
+ test("auto-wake chain: two consecutive wakes both dropped, real turn after still emits", () => {
407
+ const parser = createJsonlEventParser()
408
+ parser.parse(makeRealUser("hi"))
409
+ parser.parse(makeAssistant("hello"))
410
+ parser.parse(makeResult())
411
+ // First wake.
412
+ parser.parse(makeMetaUser("<task-notification>A done</task-notification>"))
413
+ parser.parse(makeAssistant("ack A"))
414
+ expect(resultEntries(parser.parse(makeResult()))).toEqual([])
415
+ // Second wake immediately after.
416
+ parser.parse(makeMetaUser("<task-notification>B done</task-notification>"))
417
+ parser.parse(makeAssistant("ack B"))
418
+ expect(resultEntries(parser.parse(makeResult()))).toEqual([])
419
+ // Next REAL user prompt → its result must emit.
420
+ parser.parse(makeRealUser("status?"))
421
+ parser.parse(makeAssistant("all good"))
422
+ expect(resultEntries(parser.parse(makeResult())).length).toBeGreaterThan(0)
423
+ })
424
+
425
+ test("auto-wake: assistant text inside a wake is still emitted (user sees model output)", () => {
426
+ const parser = createJsonlEventParser()
427
+ parser.parse(makeRealUser("hi"))
428
+ parser.parse(makeAssistant("hello"))
429
+ parser.parse(makeResult())
430
+ parser.parse(makeMetaUser("<task-notification>bash done</task-notification>"))
431
+ const events = parser.parse(makeAssistant("bash exited 0"))
432
+ const transcript = events.filter((e) => e.type === "transcript")
433
+ expect(transcript.length).toBeGreaterThan(0)
434
+ })
435
+ })
241
436
  })
@@ -30,6 +30,16 @@ export function createJsonlEventParser(opts: CreateJsonlEventParserOptions = {})
30
30
  let latestUsageSnapshot: ContextWindowUsageSnapshot | null = null
31
31
  let lastKnownContextWindow: number | undefined = opts.configuredContextWindow
32
32
  const detector = new ClaudeLimitDetector()
33
+ // Track turn-boundary state to filter Claude Code's background auto-wake
34
+ // turns. After a real turn ends, `useQueueProcessor` (claude-code/src/hooks/
35
+ // useQueueProcessor.ts) may auto-spawn a follow-up turn by injecting a
36
+ // synthetic `<task-notification>` user message with `isMeta:true`. Kanna
37
+ // never issued a `chat_send` for this turn, so its `result` MUST NOT
38
+ // consume a `pendingPromptSeq` (would steal a real user turn's seq) or
39
+ // alter Kanna's turn lifecycle. Mid-turn `isMeta:true` injections
40
+ // (FileReadTool metadata, token-budget continuation) appear AFTER an
41
+ // assistant message and are NOT auto-wakes — their final result is real.
42
+ let turnState: "between" | "inTurn" | "inAutoWake" = "between"
33
43
 
34
44
  return {
35
45
  parse(rawLine: string): HarnessEvent[] {
@@ -44,6 +54,38 @@ export function createJsonlEventParser(opts: CreateJsonlEventParserOptions = {})
44
54
  }
45
55
  if (!parsed || typeof parsed !== "object") return []
46
56
  const message = parsed as Record<string, unknown>
57
+ // Task subagents write their messages into the parent transcript with
58
+ // isSidechain:true. They are not part of the main turn: a sidechain
59
+ // `result` (or its TUI `turn_duration` synth) would shift the parent's
60
+ // pending prompt seq and finalize the user turn early, and a sidechain
61
+ // session_id would clobber the parent chat's claude session token.
62
+ if (message.isSidechain === true) return []
63
+
64
+ // Auto-wake detection — see turnState comment above.
65
+ const isResultLine = message.type === "result"
66
+ || (message.type === "system" && message.subtype === "turn_duration")
67
+ if (message.type === "user") {
68
+ if (message.isMeta === true && turnState === "between") {
69
+ turnState = "inAutoWake"
70
+ return []
71
+ }
72
+ if (message.isMeta !== true) {
73
+ turnState = "inTurn"
74
+ }
75
+ // Mid-turn isMeta user (turnState === "inTurn") falls through — emit
76
+ // normally; downstream consumers already handle synthetic user lines.
77
+ } else if (message.type === "assistant" && turnState === "between") {
78
+ // Defensive: assistant without a preceding user line — treat as the
79
+ // start of a real turn so the upcoming result is emitted.
80
+ turnState = "inTurn"
81
+ } else if (isResultLine) {
82
+ if (turnState === "inAutoWake") {
83
+ turnState = "between"
84
+ return []
85
+ }
86
+ turnState = "between"
87
+ }
88
+
47
89
  const events: HarnessEvent[] = []
48
90
 
49
91
  // D3 — emit session_token for any message carrying a session_id, not
@@ -149,6 +191,8 @@ export function parseJsonlLine(rawLine: string): HarnessEvent[] {
149
191
  }
150
192
  if (!parsed || typeof parsed !== "object") return []
151
193
  const message = parsed as Record<string, unknown>
194
+ // Sidechain (Task subagent) lines never belong to the main turn stream.
195
+ if (message.isSidechain === true) return []
152
196
  const events: HarnessEvent[] = []
153
197
 
154
198
  if (message.type === "system" && message.subtype === "init" && typeof message.session_id === "string") {
@@ -342,6 +342,14 @@ export const PROVIDERS: ProviderCatalogEntry[] = [
342
342
  supportsEffort: true,
343
343
  aliases: ["haiku"],
344
344
  },
345
+ {
346
+ id: "claude-opus-4-8",
347
+ label: "Opus 4.8",
348
+ supportsEffort: true,
349
+ aliases: ["opus-4-8"],
350
+ contextWindowOptions: [...CLAUDE_CONTEXT_WINDOW_OPTIONS],
351
+ supportsMaxReasoningEffort: true,
352
+ },
345
353
  ],
346
354
  efforts: [...CLAUDE_REASONING_OPTIONS],
347
355
  },
@@ -1 +0,0 @@
1
- import{U as a,D as n}from"./mermaid.core-DSDWYURW.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-727SXJPM-Ig1oTofA.js";import{_ as i}from"./mermaid.core-DSDWYURW.js";import"./chunk-FMBD7UC4-CFu3ujo5.js";import"./chunk-ND2GUHAM-CNSLqk89.js";import"./chunk-55IACEB6-DYFOLYdT.js";import"./chunk-2J33WTMH-D8N21cnl.js";import"./index-bafMJdKb.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-727SXJPM-Ig1oTofA.js";import{_ as i}from"./mermaid.core-DSDWYURW.js";import"./chunk-FMBD7UC4-CFu3ujo5.js";import"./chunk-ND2GUHAM-CNSLqk89.js";import"./chunk-55IACEB6-DYFOLYdT.js";import"./chunk-2J33WTMH-D8N21cnl.js";import"./index-bafMJdKb.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as e,b as r,a,S as s}from"./chunk-AQP2D5EJ-N4H6IIEE.js";import{_ as i}from"./mermaid.core-DSDWYURW.js";import"./chunk-55IACEB6-DYFOLYdT.js";import"./chunk-2J33WTMH-D8N21cnl.js";import"./index-bafMJdKb.js";var p={parser:a,get db(){return new s(2)},renderer:r,styles:e,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{p as diagram};