33 buildThreadTimeline ,
44 deriveThreadAgentStatus ,
55 hasAgentMention ,
6- normalizeAgentPromptText ,
76 shouldSuspendThreadSession ,
7+ threadMessageArtifact ,
88} from "./threadTimeline" ;
99
1010describe ( "hasAgentMention" , ( ) => {
@@ -19,96 +19,176 @@ describe("hasAgentMention", () => {
1919 } ) ;
2020} ) ;
2121
22- describe ( "normalizeAgentPromptText" , ( ) => {
22+ describe ( "threadMessageArtifact" , ( ) => {
23+ it ( "maps a canvas_created message to a canvas artifact" , ( ) => {
24+ expect (
25+ threadMessageArtifact ( {
26+ id : "m1" ,
27+ content :
28+ "[Signups](https://us.posthog.com/code/canvas/c/d) has been created" ,
29+ created_at : "2026-07-17T00:00:00Z" ,
30+ author_kind : "agent" ,
31+ event : "canvas_created" ,
32+ payload : {
33+ canvas_name : "Signups" ,
34+ canvas_url : "https://us.posthog.com/code/canvas/c/d" ,
35+ } ,
36+ } ) ,
37+ ) . toEqual ( {
38+ kind : "canvas" ,
39+ name : "Signups" ,
40+ url : "https://us.posthog.com/code/canvas/c/d" ,
41+ } ) ;
42+ } ) ;
43+
44+ it ( "falls back to a default canvas name and no url" , ( ) => {
45+ expect (
46+ threadMessageArtifact ( {
47+ id : "m1" ,
48+ content : "Canvas has been created" ,
49+ created_at : "2026-07-17T00:00:00Z" ,
50+ author_kind : "agent" ,
51+ event : "canvas_created" ,
52+ payload : { } ,
53+ } ) ,
54+ ) . toEqual ( { kind : "canvas" , name : "Canvas" , url : null } ) ;
55+ } ) ;
56+
57+ it ( "maps a pr_created message to a pr artifact" , ( ) => {
58+ expect (
59+ threadMessageArtifact ( {
60+ id : "m2" ,
61+ content : "Pull request opened" ,
62+ created_at : "2026-07-17T00:00:00Z" ,
63+ author_kind : "agent" ,
64+ event : "pr_created" ,
65+ payload : { pr_url : "https://github.com/org/repo/pull/123" } ,
66+ } ) ,
67+ ) . toEqual ( { kind : "pr" , url : "https://github.com/org/repo/pull/123" } ) ;
68+ } ) ;
69+
2370 it . each ( [
2471 [
25- "forwarded thread comment" ,
26- "[Thread comment from Peter Kirkham] @agent which model are you?" ,
27- "which model are you?" ,
72+ "a pr_created message without a url" ,
73+ {
74+ id : "m3" ,
75+ content : "Pull request opened" ,
76+ created_at : "2026-07-17T00:00:00Z" ,
77+ author_kind : "agent" as const ,
78+ event : "pr_created" ,
79+ payload : { } ,
80+ } ,
2881 ] ,
29- [ "direct prompt" , "which model are you?" , "which model are you?" ] ,
3082 [
31- "direct prompt with mention" ,
32- "@agent which model are you?" ,
33- "which model are you?" ,
83+ "a turn_complete message" ,
84+ {
85+ id : "m4" ,
86+ content : "@[Casey](casey@example.com) Done." ,
87+ created_at : "2026-07-17T00:00:00Z" ,
88+ author_kind : "agent" as const ,
89+ event : "turn_complete" ,
90+ payload : { run_id : "run" } ,
91+ } ,
3492 ] ,
35- ] ) ( "normalizes a %s" , ( _name , content , expected ) => {
36- expect ( normalizeAgentPromptText ( content ) ) . toBe ( expected ) ;
93+ [
94+ "a human message" ,
95+ {
96+ id : "m5" ,
97+ content : "Looks good" ,
98+ created_at : "2026-07-17T00:00:00Z" ,
99+ } ,
100+ ] ,
101+ ] ) ( "returns no artifact for %s" , ( _name , message ) => {
102+ expect ( threadMessageArtifact ( message ) ) . toBeNull ( ) ;
37103 } ) ;
38104} ) ;
39105
40106describe ( "buildThreadTimeline" , ( ) => {
41- it ( "omits the session echo of a forwarded thread message" , ( ) => {
42- const timeline = buildThreadTimeline ( {
43- prompts : [
44- {
45- id : "prompt" ,
46- text : "[Thread comment from Peter Kirkham] @agent which model are you?" ,
47- timestamp : 200 ,
48- } ,
49- ] ,
50- humanMessages : [
51- {
52- id : "human" ,
53- content : "@agent which model are you?" ,
54- createdAt : "1970-01-01T00:00:00.100Z" ,
55- forwardedToAgent : true ,
56- } ,
57- ] ,
58- agentMessages : [ ] ,
59- } ) ;
107+ it ( "keeps only human messages and artifacts" , ( ) => {
108+ const timeline = buildThreadTimeline ( [
109+ {
110+ id : "human" ,
111+ content : "Kicking this off" ,
112+ created_at : "1970-01-01T00:00:00.100Z" ,
113+ } ,
114+ {
115+ id : "turn" ,
116+ content : "@[Casey](casey@example.com) Shipped it." ,
117+ created_at : "1970-01-01T00:00:00.200Z" ,
118+ author_kind : "agent" ,
119+ event : "turn_complete" ,
120+ payload : { run_id : "run" } ,
121+ } ,
122+ {
123+ id : "system" ,
124+ content : "Status changed" ,
125+ created_at : "1970-01-01T00:00:00.250Z" ,
126+ author_kind : "system" ,
127+ event : "status_changed" ,
128+ } ,
129+ {
130+ id : "canvas" ,
131+ content : "Canvas has been created" ,
132+ created_at : "1970-01-01T00:00:00.300Z" ,
133+ author_kind : "agent" ,
134+ event : "canvas_created" ,
135+ payload : { canvas_name : "Signups" , canvas_url : null } ,
136+ } ,
137+ ] ) ;
60138
61- expect ( timeline . map ( ( row ) => row . kind ) ) . toEqual ( [ "human" ] ) ;
139+ expect ( timeline . map ( ( row ) => row . kind ) ) . toEqual ( [ "human" , "artifact" ] ) ;
62140 } ) ;
63141
64- it ( "keeps a thread-comment prompt without a matching forwarded message" , ( ) => {
65- const timeline = buildThreadTimeline ( {
66- prompts : [
67- {
68- id : "prompt" ,
69- text : "[Thread comment from Peter Kirkham] @agent which model are you?" ,
70- timestamp : 200 ,
71- } ,
72- ] ,
73- humanMessages : [ ] ,
74- agentMessages : [ ] ,
75- } ) ;
142+ it ( "orders human messages and artifacts chronologically" , ( ) => {
143+ const timeline = buildThreadTimeline ( [
144+ {
145+ id : "pr" ,
146+ content : "Pull request opened" ,
147+ created_at : "1970-01-01T00:00:00.200Z" ,
148+ author_kind : "agent" ,
149+ event : "pr_created" ,
150+ payload : { pr_url : "https://github.com/org/repo/pull/1" } ,
151+ } ,
152+ {
153+ id : "human" ,
154+ content : "Reply" ,
155+ created_at : "1970-01-01T00:00:00.100Z" ,
156+ } ,
157+ ] ) ;
76158
77- expect ( timeline . map ( ( row ) => row . kind ) ) . toEqual ( [ "prompt " ] ) ;
159+ expect ( timeline . map ( ( row ) => row . message . id ) ) . toEqual ( [ "human" , "pr "] ) ;
78160 } ) ;
79161
80- it ( "interleaves prompts, human replies, and agent turns chronologically" , ( ) => {
81- const timeline = buildThreadTimeline ( {
82- prompts : [ { id : "prompt" , text : "Start" , timestamp : 100 } ] ,
83- humanMessages : [
84- {
85- id : "human" ,
86- content : "Reply" ,
87- createdAt : "1970-01-01T00:00:00.150Z" ,
88- } ,
89- ] ,
90- agentMessages : [ { id : "agent" , text : "Done" , timestamp : 200 } ] ,
91- } ) ;
92-
93- expect ( timeline . map ( ( row ) => row . kind ) ) . toEqual ( [
94- "prompt" ,
95- "human" ,
96- "agent" ,
162+ it ( "keeps malformed timestamps at the end" , ( ) => {
163+ const timeline = buildThreadTimeline ( [
164+ { id : "broken" , content : "Reply" , created_at : "invalid" } ,
165+ {
166+ id : "human" ,
167+ content : "Reply" ,
168+ created_at : "1970-01-01T00:00:00.100Z" ,
169+ } ,
97170 ] ) ;
98- } ) ;
99171
100- it ( "keeps malformed timestamps at the end" , ( ) => {
101- const timeline = buildThreadTimeline ( {
102- prompts : [ { id : "prompt" , text : "Start" , timestamp : 100 } ] ,
103- humanMessages : [ { id : "human" , content : "Reply" , createdAt : "invalid" } ] ,
104- agentMessages : [ { id : "agent" , text : "Done" , timestamp : 200 } ] ,
105- } ) ;
172+ expect ( timeline . map ( ( row ) => row . message . id ) ) . toEqual ( [ "human" , "broken" ] ) ;
173+ } ) ;
106174
107- expect ( timeline . map ( ( row ) => row . kind ) ) . toEqual ( [
108- "prompt" ,
109- "agent" ,
110- "human" ,
175+ it ( "exposes the artifact and the source message on artifact rows" , ( ) => {
176+ const [ row ] = buildThreadTimeline ( [
177+ {
178+ id : "pr" ,
179+ content : "Pull request opened" ,
180+ created_at : "1970-01-01T00:00:00.200Z" ,
181+ author_kind : "agent" ,
182+ event : "pr_created" ,
183+ payload : { pr_url : "https://github.com/org/repo/pull/1" } ,
184+ } ,
111185 ] ) ;
186+
187+ expect ( row ) . toMatchObject ( {
188+ kind : "artifact" ,
189+ artifact : { kind : "pr" , url : "https://github.com/org/repo/pull/1" } ,
190+ message : { id : "pr" } ,
191+ } ) ;
112192 } ) ;
113193} ) ;
114194
0 commit comments