diff --git a/.data/artifacts/artifact_mo4wctmm_ltj99rgk.txt b/.data/artifacts/artifact_mo4wctmm_ltj99rgk.txt new file mode 100644 index 0000000..04a3b4a --- /dev/null +++ b/.data/artifacts/artifact_mo4wctmm_ltj99rgk.txt @@ -0,0 +1,139 @@ +LUMA +FURNİTURE ATELİER +KOLEKSİYON +MALZEME +SÜREÇ +JOURNAL +RANDEVU AL + +ÇAĞDAŞ İÇ MEKÂNLAR İÇİN HEYKELSİ MOBİLYALAR + +Mekânı doldurmayan, mekânı tanımlayan parçalar. + +Luma, yavaş yaşayan evler için modüler koltuklar, raf sistemleri ve dokulu yüzeyler üretir. Her parça ışığı, boşluğu ve günlük ritmi merkez alır. + +YENİ KOLEKSİYONU GÖR +MALZEME PALETİ +12 +özel seri tasarım +7 hafta +ortalama üretim süresi +%100 +dokulu doğal yüzey +SİGNATURE SCENE +Soft Geometry Living + +Kum tonları, alçak hacimler ve heykelsi aydınlatmalarla kurulan dingin oturma alanı. + +MATERİAL FOCUS +Traverten · Ceviz · Keten +ÖNE ÇIKAN PARÇALAR +Sessiz ama karakterli bir koleksiyon + +Keskin görünmeden net duran, yumuşak görünmeden güçlü kalan parçalar. + +SALON +Aria Modül Koltuk + +Yumuşak hatlı modüller, taş dokulu kumaş ve gün ışığını içeri alan alçak bir siluet. + +₺48.500 +YEMEK +Monolith Yemek Masası + +Traverten etkili yüzey, ince metal taşıyıcılar ve sekiz kişilik sakin bir ritim. + +₺36.200 +DEPOLAMA +Halo Açık Raf + +Ceviz tonlarında dikey bir omurga, objeleri sergileyen boşluklarla dengelenir. + +₺24.900 +YATAK ODASI +Still Yatak Sistemi + +Entegre başlık, gizli komodin ve karartılmış meşe yüzey ile sessiz bir kaçış alanı. + +₺58.000 +MALZEME ANLATISI +Dokular ışıkla birlikte çalışır + +Luma yüzeyleri dikkat çekmek için değil, mekânın sesini kısmak için seçer. + +SICAK VE KATMANLI +Doğal ceviz + +Her damarın yönü görünür bırakılır; yüzey mat kalır, mekân daha sakin hissedilir. + +YUMUŞAK VE NEFES ALAN +Keten karışım döşeme + +Işığı kıran dokusu sayesinde büyük hacimli formlar bile hafif görünür. + +NET VE ÇAĞDAŞ +Fırçalanmış metal + +Yansıtmaz, parlamaz; yalnızca çizgiyi vurgular ve mobilyayı ayakta tutar. + +CURATED İNTERİOR NOTE + +Geniş odalarda ağır değil, akışkan bir ağırlık gerekir. + +Bu yüzden formu büyütürken ayakları inceltiyor, tonları koyulaştırırken yüzeyleri matlaştırıyoruz. Sonuç: güçlü ama yormayan bir atmosfer. + +modüler kurgu +derin oturum +mat yüzey dengesi + +NEW İNTERİOR DİRECTİON · AUTUMN / WİNTER + +Yaşam alanını sergileme yerine dinlenme alanına çevir. +TASARIM SÜRECİ +Her proje bir ritim çalışmasıdır + +Alanı, alışkanlıkları ve gün sonu hissini birlikte kurguluyoruz. + +01 +Alanı okuyoruz + +Gün ışığı, sirkülasyon ve kullanım ritmini analiz edip boşlukları tanımlıyoruz. + +02 +Formu kuruyoruz + +Modül, malzeme ve oran kararlarını tek bir çizgide birleştiriyoruz. + +03 +Yüzeyi sessizleştiriyoruz + +Renk, doku ve dikiş detaylarını rafine edip sade ama zengin bir sonuç çıkarıyoruz. + +“Ev ilk bakışta değil, akşam oturduğunda nasıl hissettirdiğiyle hatırlanır.” + +LUMA İNTERİOR DİRECTİON +JOURNAL +Yaşam biçimi notları + +Mobilyayı nesne olarak değil, bir ruh hâli aracı olarak ele alan kısa okumalar. + +KÜRASYON +Az eşya, güçlü hacim + +Bir odada daha az parça kullanıp daha fazla etki yaratmanın yolları. + +MALZEME +Mat yüzey neden iyi yaşlanır? + +Parlaklık yerine derinlik tercih edildiğinde mobilya zamana daha iyi uyum sağlar. + +YAŞAM +Yavaşlayan akşam rutinleri + +Dinlenme alanlarını daha sıcak ve daha akışkan hale getiren küçük kararlar. + +PRİVATE CONSULTATİON + +Yeni evin için malzeme ve yerleşim seçimini birlikte kuralım. +HELLO@LUMAFURNİTURE.COM ++90 212 000 00 00 \ No newline at end of file diff --git a/.data/artifacts/artifact_mo4wctmo_xv8olpy5.txt b/.data/artifacts/artifact_mo4wctmo_xv8olpy5.txt new file mode 100644 index 0000000..27cfab0 --- /dev/null +++ b/.data/artifacts/artifact_mo4wctmo_xv8olpy5.txt @@ -0,0 +1,12 @@ +[ + { + "type": "error", + "text": "Failed to load resource: the server responded with a status of 404 (Not Found)", + "location": { + "url": "http://127.0.0.1:4173/favicon.ico", + "lineNumber": 0, + "columnNumber": 0 + }, + "timestamp": "2026-04-18T22:16:29.574Z" + } +] \ No newline at end of file diff --git a/.data/artifacts/artifact_mo4wctms_xayl488b.txt b/.data/artifacts/artifact_mo4wctms_xayl488b.txt new file mode 100644 index 0000000..b32bbd5 --- /dev/null +++ b/.data/artifacts/artifact_mo4wctms_xayl488b.txt @@ -0,0 +1,32 @@ +{ + "url": "http://127.0.0.1:4173/", + "title": "Luma Furniture", + "summary": { + "headings": [ + "Mekânı doldurmayan, mekânı tanımlayan parçalar.", + "Soft Geometry Living", + "Traverten · Ceviz · Keten", + "Sessiz ama karakterli bir koleksiyon", + "Aria Modül Koltuk", + "Monolith Yemek Masası", + "Halo Açık Raf", + "Still Yatak Sistemi", + "Dokular ışıkla birlikte çalışır", + "Doğal ceviz", + "Keten karışım döşeme", + "Fırçalanmış metal", + "Geniş odalarda ağır değil, akışkan bir ağırlık gerekir.", + "Yaşam alanını sergileme yerine dinlenme alanına çevir.", + "Her proje bir ritim çalışmasıdır", + "Alanı okuyoruz", + "Formu kuruyoruz", + "Yüzeyi sessizleştiriyoruz", + "Yaşam biçimi notları", + "Az eşya, güçlü hacim" + ], + "links": 9, + "buttons": 0, + "forms": 0, + "inputs": 0 + } +} \ No newline at end of file diff --git a/.data/artifacts/artifact_mo4wd026_g4ll3ev3.txt b/.data/artifacts/artifact_mo4wd026_g4ll3ev3.txt new file mode 100644 index 0000000..4e22c57 --- /dev/null +++ b/.data/artifacts/artifact_mo4wd026_g4ll3ev3.txt @@ -0,0 +1,7 @@ +{ + "innerWidth": 1440, + "scrollWidth": 1425, + "hasHorizontalOverflow": false, + "heroHeight": 1049.4375, + "topbarHeight": 85.265625 +} \ No newline at end of file diff --git a/.data/artifacts/artifact_mo4wd8km_b06quyjz.txt b/.data/artifacts/artifact_mo4wd8km_b06quyjz.txt new file mode 100644 index 0000000..5647f48 --- /dev/null +++ b/.data/artifacts/artifact_mo4wd8km_b06quyjz.txt @@ -0,0 +1,8 @@ +{ + "innerWidth": 390, + "scrollWidth": 412, + "hasHorizontalOverflow": true, + "heroHeight": 1564.390625, + "topbarHeight": 185.640625, + "navHeight": 17.265625 +} \ No newline at end of file diff --git a/.data/artifacts/artifact_mo4wd8lh_61mzru65.txt b/.data/artifacts/artifact_mo4wd8lh_61mzru65.txt new file mode 100644 index 0000000..eafe640 --- /dev/null +++ b/.data/artifacts/artifact_mo4wd8lh_61mzru65.txt @@ -0,0 +1,12 @@ +[ + { + "type": "error", + "text": "Failed to load resource: the server responded with a status of 404 (Not Found)", + "location": { + "url": "http://127.0.0.1:4173/favicon.ico", + "lineNumber": 0, + "columnNumber": 0 + }, + "timestamp": "2026-04-18T22:16:56.230Z" + } +] \ No newline at end of file diff --git a/.data/artifacts/artifact_mo4wddlx_7t6jxcyd.txt b/.data/artifacts/artifact_mo4wddlx_7t6jxcyd.txt new file mode 100644 index 0000000..32316ce --- /dev/null +++ b/.data/artifacts/artifact_mo4wddlx_7t6jxcyd.txt @@ -0,0 +1,20 @@ +[ + { + "tag": "div", + "cls": "hero-copy", + "text": "Çağdaş iç mekânlar için heykelsi mobilya", + "left": 10, + "right": 412, + "width": 402, + "overflow": 22 + }, + { + "tag": "div", + "cls": "hero-visual", + "text": "Signature SceneSoft Geometry LivingKum t", + "left": 10, + "right": 412, + "width": 402, + "overflow": 22 + } +] \ No newline at end of file diff --git a/.data/artifacts/artifact_mo4wdio2_2qoatfwk.txt b/.data/artifacts/artifact_mo4wdio2_2qoatfwk.txt new file mode 100644 index 0000000..4cee6c6 --- /dev/null +++ b/.data/artifacts/artifact_mo4wdio2_2qoatfwk.txt @@ -0,0 +1,68 @@ +{ + "body": { + "width": 375, + "left": -0.0625, + "right": 374.9375, + "marginLeft": "0px", + "marginRight": "0px", + "paddingLeft": "0px", + "paddingRight": "0px", + "boxSizing": "border-box", + "display": "block" + }, + "#app": { + "width": 375, + "left": -0.0625, + "right": 374.9375, + "marginLeft": "0px", + "marginRight": "0px", + "paddingLeft": "0px", + "paddingRight": "0px", + "boxSizing": "border-box", + "display": "block" + }, + ".page-shell": { + "width": 355, + "left": 9.9375, + "right": 364.9375, + "marginLeft": "10px", + "marginRight": "10px", + "paddingLeft": "0px", + "paddingRight": "0px", + "boxSizing": "border-box", + "display": "block" + }, + ".hero-grid": { + "width": 355, + "left": 9.9375, + "right": 364.9375, + "marginLeft": "0px", + "marginRight": "0px", + "paddingLeft": "0px", + "paddingRight": "0px", + "boxSizing": "border-box", + "display": "grid" + }, + ".hero-copy": { + "width": 401.828125, + "left": 9.9375, + "right": 411.765625, + "marginLeft": "0px", + "marginRight": "0px", + "paddingLeft": "32px", + "paddingRight": "32px", + "boxSizing": "border-box", + "display": "flex" + }, + ".hero-visual": { + "width": 401.828125, + "left": 9.9375, + "right": 411.765625, + "marginLeft": "0px", + "marginRight": "0px", + "paddingLeft": "24px", + "paddingRight": "24px", + "boxSizing": "border-box", + "display": "block" + } +} \ No newline at end of file diff --git a/.data/artifacts/artifact_mo4wdo9r_ow6fr6w6.txt b/.data/artifacts/artifact_mo4wdo9r_ow6fr6w6.txt new file mode 100644 index 0000000..8f8dcee --- /dev/null +++ b/.data/artifacts/artifact_mo4wdo9r_ow6fr6w6.txt @@ -0,0 +1,138 @@ +[ + { + "tag": "p", + "cls": "kicker", + "text": "Çağdaş iç mekânlar için heykelsi mobilyalar", + "width": 336, + "right": 379, + "overflow": 14 + }, + { + "tag": "h1", + "cls": "", + "text": "Mekânı doldurmayan, mekânı tanımlayan parçalar.", + "width": 336, + "right": 379, + "overflow": 14 + }, + { + "tag": "p", + "cls": "hero-text", + "text": "Luma, yavaş yaşayan evler için modüler koltuklar, raf sistem", + "width": 336, + "right": 379, + "overflow": 14 + }, + { + "tag": "div", + "cls": "hero-actions", + "text": "Yeni Koleksiyonu GörMalzeme Paleti", + "width": 336, + "right": 379, + "overflow": 14 + }, + { + "tag": "div", + "cls": "hero-metrics", + "text": "12özel seri tasarım7 haftaortalama üretim süresi%100dokulu d", + "width": 336, + "right": 379, + "overflow": 14 + }, + { + "tag": "div", + "cls": "", + "text": "12özel seri tasarım", + "width": 336, + "right": 379, + "overflow": 14 + }, + { + "tag": "strong", + "cls": "", + "text": "12", + "width": 336, + "right": 379, + "overflow": 14 + }, + { + "tag": "div", + "cls": "", + "text": "7 haftaortalama üretim süresi", + "width": 336, + "right": 379, + "overflow": 14 + }, + { + "tag": "strong", + "cls": "", + "text": "7 hafta", + "width": 336, + "right": 379, + "overflow": 14 + }, + { + "tag": "div", + "cls": "", + "text": "%100dokulu doğal yüzey", + "width": 336, + "right": 379, + "overflow": 14 + }, + { + "tag": "strong", + "cls": "", + "text": "%100", + "width": 336, + "right": 379, + "overflow": 14 + }, + { + "tag": "span", + "cls": "", + "text": "mekânı tanımlayan", + "width": 268, + "right": 311, + "overflow": -54 + }, + { + "tag": "a", + "cls": "primary-button", + "text": "Yeni Koleksiyonu Gör", + "width": 259, + "right": 302, + "overflow": -63 + }, + { + "tag": "span", + "cls": "", + "text": "ortalama üretim süresi", + "width": 152, + "right": 195, + "overflow": -170 + }, + { + "tag": "span", + "cls": "", + "text": "dokulu doğal yüzey", + "width": 133, + "right": 176, + "overflow": -189 + }, + { + "tag": "span", + "cls": "", + "text": "özel seri tasarım", + "width": 112, + "right": 155, + "overflow": -210 + }, + { + "tag": "a", + "cls": "text-link", + "text": "Malzeme Paleti", + "width": 111, + "right": 154, + "overflow": -211 + } +] \ No newline at end of file diff --git a/.data/artifacts/artifact_mo4wfi9a_9uo2btyo.txt b/.data/artifacts/artifact_mo4wfi9a_9uo2btyo.txt new file mode 100644 index 0000000..bee0473 --- /dev/null +++ b/.data/artifacts/artifact_mo4wfi9a_9uo2btyo.txt @@ -0,0 +1,8 @@ +{ + "innerWidth": 390, + "scrollWidth": 379, + "hasHorizontalOverflow": false, + "heroCopyWidth": 355, + "heroVisualWidth": 355, + "pageShellWidth": 355 +} \ No newline at end of file diff --git a/.data/artifacts/artifact_mo4wfiad_qdh56vqu.txt b/.data/artifacts/artifact_mo4wfiad_qdh56vqu.txt new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/.data/artifacts/artifact_mo4wfiad_qdh56vqu.txt @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/.data/artifacts/artifact_mo4wfqm2_q2xwfxa2.txt b/.data/artifacts/artifact_mo4wfqm2_q2xwfxa2.txt new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/.data/artifacts/artifact_mo4wfqm2_q2xwfxa2.txt @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/.data/artifacts/artifact_mo4wfqm3_x2ai16as.txt b/.data/artifacts/artifact_mo4wfqm3_x2ai16as.txt new file mode 100644 index 0000000..ebd2ed2 --- /dev/null +++ b/.data/artifacts/artifact_mo4wfqm3_x2ai16as.txt @@ -0,0 +1,5 @@ +{ + "innerWidth": 1440, + "scrollWidth": 1425, + "hasHorizontalOverflow": false +} \ No newline at end of file diff --git a/.data/artifacts/index.json b/.data/artifacts/index.json new file mode 100644 index 0000000..2d4aea4 --- /dev/null +++ b/.data/artifacts/index.json @@ -0,0 +1,206 @@ +{ + "artifact_mo4wctmm_ltj99rgk": { + "artifact_id": "artifact_mo4wctmm_ltj99rgk", + "kind": "markdown", + "created_at": "2026-04-18T22:16:36.862Z", + "storage_path": "/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wctmm_ltj99rgk.txt", + "bytes": 3416, + "session_id": "session_mo4wclhc_lto1vdqi", + "page_id": "page_mo4wcltv_o5uyk2mr", + "action_id": "action_mo4wctmj_tvms4x1c", + "url": "http://127.0.0.1:4173/", + "title": "Luma Furniture", + "meta": { + "requested_format": "markdown", + "truncated": false + } + }, + "artifact_mo4wctmo_xv8olpy5": { + "artifact_id": "artifact_mo4wctmo_xv8olpy5", + "kind": "console", + "created_at": "2026-04-18T22:16:36.864Z", + "storage_path": "/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wctmo_xv8olpy5.txt", + "bytes": 292, + "session_id": "session_mo4wclhc_lto1vdqi", + "page_id": "page_mo4wcltv_o5uyk2mr", + "action_id": "action_mo4wctmo_kf3373mj", + "url": "http://127.0.0.1:4173/", + "title": "Luma Furniture", + "meta": { + "limit": 50, + "entry_count": 1 + } + }, + "artifact_mo4wctms_xayl488b": { + "artifact_id": "artifact_mo4wctms_xayl488b", + "kind": "dom", + "created_at": "2026-04-18T22:16:36.868Z", + "storage_path": "/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wctms_xayl488b.txt", + "bytes": 946, + "session_id": "session_mo4wclhc_lto1vdqi", + "page_id": "page_mo4wcltv_o5uyk2mr", + "action_id": "action_mo4wctmr_vvu0bopv", + "url": "http://127.0.0.1:4173/", + "title": "Luma Furniture" + }, + "artifact_mo4wd026_g4ll3ev3": { + "artifact_id": "artifact_mo4wd026_g4ll3ev3", + "kind": "eval", + "created_at": "2026-04-18T22:16:45.198Z", + "storage_path": "/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wd026_g4ll3ev3.txt", + "bytes": 137, + "session_id": "session_mo4wclhc_lto1vdqi", + "page_id": "page_mo4wcltv_o5uyk2mr", + "action_id": "action_mo4wd023_k49m4fm4", + "url": "http://127.0.0.1:4173/", + "title": "Luma Furniture", + "meta": { + "await_promise": true, + "inline": true + } + }, + "artifact_mo4wd8km_b06quyjz": { + "artifact_id": "artifact_mo4wd8km_b06quyjz", + "kind": "eval", + "created_at": "2026-04-18T22:16:56.230Z", + "storage_path": "/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wd8km_b06quyjz.txt", + "bytes": 163, + "session_id": "session_mo4wd2gr_bd4gu6hs", + "page_id": "page_mo4wd2lg_383zitmf", + "action_id": "action_mo4wd8ki_kn4o27zi", + "url": "http://127.0.0.1:4173/", + "title": "Luma Furniture", + "meta": { + "await_promise": true, + "inline": true + } + }, + "artifact_mo4wd8lh_61mzru65": { + "artifact_id": "artifact_mo4wd8lh_61mzru65", + "kind": "console", + "created_at": "2026-04-18T22:16:56.261Z", + "storage_path": "/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wd8lh_61mzru65.txt", + "bytes": 292, + "session_id": "session_mo4wd2gr_bd4gu6hs", + "page_id": "page_mo4wd2lg_383zitmf", + "action_id": "action_mo4wd8lh_26192ual", + "url": "http://127.0.0.1:4173/", + "title": "Luma Furniture", + "meta": { + "limit": 20, + "entry_count": 1 + } + }, + "artifact_mo4wddlx_7t6jxcyd": { + "artifact_id": "artifact_mo4wddlx_7t6jxcyd", + "kind": "eval", + "created_at": "2026-04-18T22:17:02.757Z", + "storage_path": "/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wddlx_7t6jxcyd.txt", + "bytes": 366, + "session_id": "session_mo4wd2gr_bd4gu6hs", + "page_id": "page_mo4wd2lg_383zitmf", + "action_id": "action_mo4wddlu_5qh277ag", + "url": "http://127.0.0.1:4173/", + "title": "Luma Furniture", + "meta": { + "await_promise": true, + "inline": true + } + }, + "artifact_mo4wdio2_2qoatfwk": { + "artifact_id": "artifact_mo4wdio2_2qoatfwk", + "kind": "eval", + "created_at": "2026-04-18T22:17:09.314Z", + "storage_path": "/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wdio2_2qoatfwk.txt", + "bytes": 1469, + "session_id": "session_mo4wd2gr_bd4gu6hs", + "page_id": "page_mo4wd2lg_383zitmf", + "action_id": "action_mo4wdinz_44rfbhf4", + "url": "http://127.0.0.1:4173/", + "title": "Luma Furniture", + "meta": { + "await_promise": true, + "inline": true + } + }, + "artifact_mo4wdo9r_ow6fr6w6": { + "artifact_id": "artifact_mo4wdo9r_ow6fr6w6", + "kind": "eval", + "created_at": "2026-04-18T22:17:16.575Z", + "storage_path": "/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wdo9r_ow6fr6w6.txt", + "bytes": 2471, + "session_id": "session_mo4wd2gr_bd4gu6hs", + "page_id": "page_mo4wd2lg_383zitmf", + "action_id": "action_mo4wdo9o_jthvr182", + "url": "http://127.0.0.1:4173/", + "title": "Luma Furniture", + "meta": { + "await_promise": true, + "inline": true + } + }, + "artifact_mo4wfi9a_9uo2btyo": { + "artifact_id": "artifact_mo4wfi9a_9uo2btyo", + "kind": "eval", + "created_at": "2026-04-18T22:18:42.094Z", + "storage_path": "/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wfi9a_9uo2btyo.txt", + "bytes": 154, + "session_id": "session_mo4wfctl_ayv0rkvy", + "page_id": "page_mo4wfcy2_3nlfae17", + "action_id": "action_mo4wfi96_skmxw5ud", + "url": "http://127.0.0.1:4173/", + "title": "Luma Furniture", + "meta": { + "await_promise": true, + "inline": true + } + }, + "artifact_mo4wfiad_qdh56vqu": { + "artifact_id": "artifact_mo4wfiad_qdh56vqu", + "kind": "console", + "created_at": "2026-04-18T22:18:42.133Z", + "storage_path": "/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wfiad_qdh56vqu.txt", + "bytes": 2, + "session_id": "session_mo4wfctl_ayv0rkvy", + "page_id": "page_mo4wfcy2_3nlfae17", + "action_id": "action_mo4wfiad_ibikqcnu", + "url": "http://127.0.0.1:4173/", + "title": "Luma Furniture", + "meta": { + "limit": 20, + "entry_count": 0 + } + }, + "artifact_mo4wfqm2_q2xwfxa2": { + "artifact_id": "artifact_mo4wfqm2_q2xwfxa2", + "kind": "console", + "created_at": "2026-04-18T22:18:52.923Z", + "storage_path": "/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wfqm2_q2xwfxa2.txt", + "bytes": 2, + "session_id": "session_mo4wflwn_16vwiof3", + "page_id": "page_mo4wfm0v_zi6qkdwb", + "action_id": "action_mo4wfqm2_lei4ios9", + "url": "http://127.0.0.1:4173/", + "title": "Luma Furniture", + "meta": { + "limit": 20, + "entry_count": 0 + } + }, + "artifact_mo4wfqm3_x2ai16as": { + "artifact_id": "artifact_mo4wfqm3_x2ai16as", + "kind": "eval", + "created_at": "2026-04-18T22:18:52.924Z", + "storage_path": "/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wfqm3_x2ai16as.txt", + "bytes": 81, + "session_id": "session_mo4wflwn_16vwiof3", + "page_id": "page_mo4wfm0v_zi6qkdwb", + "action_id": "action_mo4wfqm2_z30ieemu", + "url": "http://127.0.0.1:4173/", + "title": "Luma Furniture", + "meta": { + "await_promise": true, + "inline": true + } + } +} diff --git a/.data/history/actions.jsonl b/.data/history/actions.jsonl new file mode 100644 index 0000000..b29ed89 --- /dev/null +++ b/.data/history/actions.jsonl @@ -0,0 +1,50 @@ +{"action_id":"action_mo4wclhb_8brvd12b","kind":"session.create","status":"started","started_at":"2026-04-18T22:16:26.303Z","input_summary":{"profile_mode":"ephemeral","locale":"en-US","timezone_id":"UTC","user_data_dir":"","profile_directory":"","humanize":false,"launch_args":[],"viewport":{"width":1440,"height":1100}}} +{"action_id":"action_mo4wclhb_8brvd12b","kind":"session.create","status":"succeeded","started_at":"2026-04-18T22:16:26.303Z","input_summary":{"profile_mode":"ephemeral","locale":"en-US","timezone_id":"UTC","user_data_dir":"","profile_directory":"","humanize":false,"launch_args":[],"viewport":{"width":1440,"height":1100}},"ended_at":"2026-04-18T22:16:26.755Z","result_summary":{"session_id":"session_mo4wclhc_lto1vdqi","context_id":"context_mo4wcltv_ya5bgce2","page_id":"page_mo4wcltv_o5uyk2mr","status":"active","profile_mode":"ephemeral","locale":"en-US","timezone_id":"UTC","user_data_dir":"","profile_directory":"","humanize":false,"launch_args":[],"viewport":{"width":1440,"height":1100},"created_at":"2026-04-18T22:16:26.755Z","health":{"consecutive_errors":0,"restart_recommended":false},"capabilities":{"observe":true,"screenshot":true,"evaluate":true}}} +{"action_id":"action_mo4wcnys_195nna3g","kind":"page.navigate","status":"started","started_at":"2026-04-18T22:16:29.524Z","input_summary":{"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","url":"http://127.0.0.1:4173/","wait_until":"load"},"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr"} +{"action_id":"action_mo4wcnys_195nna3g","kind":"page.navigate","status":"succeeded","started_at":"2026-04-18T22:16:29.524Z","input_summary":{"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","url":"http://127.0.0.1:4173/","wait_until":"load"},"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","ended_at":"2026-04-18T22:16:29.574Z","result_summary":{"page_id":"page_mo4wcltv_o5uyk2mr","requested_url":"http://127.0.0.1:4173/","final_url":"http://127.0.0.1:4173/","title":"Luma Furniture","navigation_id":"action_mo4wco06_5su3libd","timings":{"elapsed_ms":50},"follow_up_by_goal":{"interact":{"recommended_tool":"observe.a11y","screenshot_mode":"none","rationale":["Interaction tasks should start with structured page state, not screenshots."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]},"read":{"recommended_tool":"observe.text","screenshot_mode":"none","rationale":["Reading tasks should prefer cheap textual context before any visual artifact."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]},"visual_verify":{"recommended_tool":"observe.screenshot","screenshot_mode":"viewport","rationale":["Visual verification explicitly needs pixels, so screenshot escalation is justified."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]},"debug":{"recommended_tool":"observe.a11y","screenshot_mode":"none","rationale":["Debug flows usually need structural context before expensive screenshots.","A screenshot is useful only after structural context is insufficient."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]},"gesture":{"recommended_tool":"observe.boxes","screenshot_mode":"none","rationale":["Gesture execution needs precise spatial data before acting.","Bounding boxes are cheaper than screenshots and usually sufficient for coordinates."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]}}}} +{"action_id":"action_mo4wctmj_tvms4x1c","kind":"observe.text","status":"started","started_at":"2026-04-18T22:16:36.859Z","input_summary":{"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","format":"markdown"},"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr"} +{"action_id":"action_mo4wctmj_tvms4x1c","kind":"observe.text","status":"succeeded","started_at":"2026-04-18T22:16:36.859Z","input_summary":{"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","format":"markdown"},"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","ended_at":"2026-04-18T22:16:36.863Z","result_summary":{"artifact_id":"artifact_mo4wctmm_ltj99rgk","truncated":false}} +{"action_id":"action_mo4wctmo_kf3373mj","kind":"observe.console","status":"started","started_at":"2026-04-18T22:16:36.864Z","input_summary":{"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","limit":50},"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr"} +{"action_id":"action_mo4wctmo_kf3373mj","kind":"observe.console","status":"succeeded","started_at":"2026-04-18T22:16:36.864Z","input_summary":{"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","limit":50},"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","ended_at":"2026-04-18T22:16:36.864Z","result_summary":{"artifact_id":"artifact_mo4wctmo_xv8olpy5","entry_count":1}} +{"action_id":"action_mo4wctmr_vvu0bopv","kind":"observe.dom","status":"started","started_at":"2026-04-18T22:16:36.867Z","input_summary":{"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr"},"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr"} +{"action_id":"action_mo4wctmr_vvu0bopv","kind":"observe.dom","status":"succeeded","started_at":"2026-04-18T22:16:36.867Z","input_summary":{"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr"},"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","ended_at":"2026-04-18T22:16:36.868Z","result_summary":{"snapshot_id":"artifact_mo4wctms_xayl488b"}} +{"action_id":"action_mo4wd023_k49m4fm4","kind":"runtime.evaluate_js","status":"started","started_at":"2026-04-18T22:16:45.195Z","input_summary":{"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","expression":"(() => ({ innerWidth: window.innerWidth, scrollWidth: document.documentElement.scrollWidth, hasHorizontalOverflow: document.documentElement.scrollWidth > window.innerWidth, heroHeight: document.querySelector('.hero-grid')?.getBoundingClientRect().height, topbarHeight: document.querySelector('.topbar')?.getBoundingClientRect().height }))()","await_promise":true},"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr"} +{"action_id":"action_mo4wd023_k49m4fm4","kind":"runtime.evaluate_js","status":"succeeded","started_at":"2026-04-18T22:16:45.195Z","input_summary":{"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","expression":"(() => ({ innerWidth: window.innerWidth, scrollWidth: document.documentElement.scrollWidth, hasHorizontalOverflow: document.documentElement.scrollWidth > window.innerWidth, heroHeight: document.querySelector('.hero-grid')?.getBoundingClientRect().height, topbarHeight: document.querySelector('.topbar')?.getBoundingClientRect().height }))()","await_promise":true},"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","ended_at":"2026-04-18T22:16:45.198Z","result_summary":{"artifact_id":"artifact_mo4wd026_g4ll3ev3","truncated":false}} +{"action_id":"action_mo4wd2gr_lsjr8wel","kind":"session.create","status":"started","started_at":"2026-04-18T22:16:48.315Z","input_summary":{"profile_mode":"ephemeral","locale":"en-US","timezone_id":"UTC","user_data_dir":"","profile_directory":"","humanize":false,"launch_args":[],"viewport":{"width":390,"height":844}}} +{"action_id":"action_mo4wd2gr_lsjr8wel","kind":"session.create","status":"succeeded","started_at":"2026-04-18T22:16:48.315Z","input_summary":{"profile_mode":"ephemeral","locale":"en-US","timezone_id":"UTC","user_data_dir":"","profile_directory":"","humanize":false,"launch_args":[],"viewport":{"width":390,"height":844}},"ended_at":"2026-04-18T22:16:48.484Z","result_summary":{"session_id":"session_mo4wd2gr_bd4gu6hs","context_id":"context_mo4wd2lg_osnlz1s6","page_id":"page_mo4wd2lg_383zitmf","status":"active","profile_mode":"ephemeral","locale":"en-US","timezone_id":"UTC","user_data_dir":"","profile_directory":"","humanize":false,"launch_args":[],"viewport":{"width":390,"height":844},"created_at":"2026-04-18T22:16:48.484Z","health":{"consecutive_errors":0,"restart_recommended":false},"capabilities":{"observe":true,"screenshot":true,"evaluate":true}}} +{"action_id":"action_mo4wd8jg_nudj3n99","kind":"page.navigate","status":"started","started_at":"2026-04-18T22:16:56.188Z","input_summary":{"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","url":"http://127.0.0.1:4173/","wait_until":"load"},"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf"} +{"action_id":"action_mo4wd8ki_kn4o27zi","kind":"runtime.evaluate_js","status":"started","started_at":"2026-04-18T22:16:56.226Z","input_summary":{"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","expression":"(() => ({ innerWidth: window.innerWidth, scrollWidth: document.documentElement.scrollWidth, hasHorizontalOverflow: document.documentElement.scrollWidth > window.innerWidth, heroHeight: document.querySelector('.hero-grid')?.getBoundingClientRect().height, topbarHeight: document.querySelector('.topbar')?.getBoundingClientRect().height, navHeight: document.querySelector('.topnav')?.getBoundingClientRect().height }))()","await_promise":true},"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf"} +{"action_id":"action_mo4wd8jg_nudj3n99","kind":"page.navigate","status":"succeeded","started_at":"2026-04-18T22:16:56.188Z","input_summary":{"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","url":"http://127.0.0.1:4173/","wait_until":"load"},"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","ended_at":"2026-04-18T22:16:56.229Z","result_summary":{"page_id":"page_mo4wd2lg_383zitmf","requested_url":"http://127.0.0.1:4173/","final_url":"http://127.0.0.1:4173/","title":"Luma Furniture","navigation_id":"action_mo4wd8kl_usux87eu","timings":{"elapsed_ms":41},"follow_up_by_goal":{"interact":{"recommended_tool":"observe.a11y","screenshot_mode":"none","rationale":["Interaction tasks should start with structured page state, not screenshots."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]},"read":{"recommended_tool":"observe.text","screenshot_mode":"none","rationale":["Reading tasks should prefer cheap textual context before any visual artifact."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]},"visual_verify":{"recommended_tool":"observe.screenshot","screenshot_mode":"viewport","rationale":["Visual verification explicitly needs pixels, so screenshot escalation is justified."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]},"debug":{"recommended_tool":"observe.a11y","screenshot_mode":"none","rationale":["Debug flows usually need structural context before expensive screenshots.","A screenshot is useful only after structural context is insufficient."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]},"gesture":{"recommended_tool":"observe.boxes","screenshot_mode":"none","rationale":["Gesture execution needs precise spatial data before acting.","Bounding boxes are cheaper than screenshots and usually sufficient for coordinates."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]}}}} +{"action_id":"action_mo4wd8ki_kn4o27zi","kind":"runtime.evaluate_js","status":"succeeded","started_at":"2026-04-18T22:16:56.226Z","input_summary":{"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","expression":"(() => ({ innerWidth: window.innerWidth, scrollWidth: document.documentElement.scrollWidth, hasHorizontalOverflow: document.documentElement.scrollWidth > window.innerWidth, heroHeight: document.querySelector('.hero-grid')?.getBoundingClientRect().height, topbarHeight: document.querySelector('.topbar')?.getBoundingClientRect().height, navHeight: document.querySelector('.topnav')?.getBoundingClientRect().height }))()","await_promise":true},"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","ended_at":"2026-04-18T22:16:56.230Z","result_summary":{"artifact_id":"artifact_mo4wd8km_b06quyjz","truncated":false}} +{"action_id":"action_mo4wd8lh_26192ual","kind":"observe.console","status":"started","started_at":"2026-04-18T22:16:56.261Z","input_summary":{"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","limit":20},"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf"} +{"action_id":"action_mo4wd8lh_26192ual","kind":"observe.console","status":"succeeded","started_at":"2026-04-18T22:16:56.261Z","input_summary":{"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","limit":20},"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","ended_at":"2026-04-18T22:16:56.261Z","result_summary":{"artifact_id":"artifact_mo4wd8lh_61mzru65","entry_count":1}} +{"action_id":"action_mo4wddlu_5qh277ag","kind":"runtime.evaluate_js","status":"started","started_at":"2026-04-18T22:17:02.754Z","input_summary":{"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","expression":"(() => Array.from(document.querySelectorAll('body *')).map(el => { const r = el.getBoundingClientRect(); return { tag: el.tagName.toLowerCase(), cls: el.className || '', text: (el.textContent || '').trim().slice(0, 40), left: Math.round(r.left), right: Math.round(r.right), width: Math.round(r.width), overflow: Math.round(r.right - window.innerWidth) }; }).filter(x => x.right > window.innerWidth + 1).sort((a,b) => b.right - a.right).slice(0, 20))()","await_promise":true},"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf"} +{"action_id":"action_mo4wddlu_5qh277ag","kind":"runtime.evaluate_js","status":"succeeded","started_at":"2026-04-18T22:17:02.754Z","input_summary":{"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","expression":"(() => Array.from(document.querySelectorAll('body *')).map(el => { const r = el.getBoundingClientRect(); return { tag: el.tagName.toLowerCase(), cls: el.className || '', text: (el.textContent || '').trim().slice(0, 40), left: Math.round(r.left), right: Math.round(r.right), width: Math.round(r.width), overflow: Math.round(r.right - window.innerWidth) }; }).filter(x => x.right > window.innerWidth + 1).sort((a,b) => b.right - a.right).slice(0, 20))()","await_promise":true},"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","ended_at":"2026-04-18T22:17:02.757Z","result_summary":{"artifact_id":"artifact_mo4wddlx_7t6jxcyd","truncated":false}} +{"action_id":"action_mo4wdinz_44rfbhf4","kind":"runtime.evaluate_js","status":"started","started_at":"2026-04-18T22:17:09.311Z","input_summary":{"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","expression":"(() => { const sels=['body','#app','.page-shell','.hero-grid','.hero-copy','.hero-visual']; return Object.fromEntries(sels.map(sel => { const el=document.querySelector(sel); if(!el) return [sel,null]; const r=el.getBoundingClientRect(); const cs=getComputedStyle(el); return [sel,{width:r.width,left:r.left,right:r.right,marginLeft:cs.marginLeft,marginRight:cs.marginRight,paddingLeft:cs.paddingLeft,paddingRight:cs.paddingRight,boxSizing:cs.boxSizing,display:cs.display}]; })); })()","await_promise":true},"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf"} +{"action_id":"action_mo4wdinz_44rfbhf4","kind":"runtime.evaluate_js","status":"succeeded","started_at":"2026-04-18T22:17:09.311Z","input_summary":{"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","expression":"(() => { const sels=['body','#app','.page-shell','.hero-grid','.hero-copy','.hero-visual']; return Object.fromEntries(sels.map(sel => { const el=document.querySelector(sel); if(!el) return [sel,null]; const r=el.getBoundingClientRect(); const cs=getComputedStyle(el); return [sel,{width:r.width,left:r.left,right:r.right,marginLeft:cs.marginLeft,marginRight:cs.marginRight,paddingLeft:cs.paddingLeft,paddingRight:cs.paddingRight,boxSizing:cs.boxSizing,display:cs.display}]; })); })()","await_promise":true},"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","ended_at":"2026-04-18T22:17:09.314Z","result_summary":{"artifact_id":"artifact_mo4wdio2_2qoatfwk","truncated":false}} +{"action_id":"action_mo4wdo9o_jthvr182","kind":"runtime.evaluate_js","status":"started","started_at":"2026-04-18T22:17:16.572Z","input_summary":{"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","expression":"(() => Array.from(document.querySelectorAll('.hero-copy *')).map(el => { const r = el.getBoundingClientRect(); return { tag: el.tagName.toLowerCase(), cls: el.className || '', text: (el.textContent || '').trim().replace(/\\s+/g,' ').slice(0,60), width: Math.round(r.width), right: Math.round(r.right), overflow: Math.round(r.right - document.querySelector('.page-shell').getBoundingClientRect().right) }; }).sort((a,b) => b.width - a.width).slice(0,20))()","await_promise":true},"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf"} +{"action_id":"action_mo4wdo9o_jthvr182","kind":"runtime.evaluate_js","status":"succeeded","started_at":"2026-04-18T22:17:16.572Z","input_summary":{"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","expression":"(() => Array.from(document.querySelectorAll('.hero-copy *')).map(el => { const r = el.getBoundingClientRect(); return { tag: el.tagName.toLowerCase(), cls: el.className || '', text: (el.textContent || '').trim().replace(/\\s+/g,' ').slice(0,60), width: Math.round(r.width), right: Math.round(r.right), overflow: Math.round(r.right - document.querySelector('.page-shell').getBoundingClientRect().right) }; }).sort((a,b) => b.width - a.width).slice(0,20))()","await_promise":true},"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","ended_at":"2026-04-18T22:17:16.575Z","result_summary":{"artifact_id":"artifact_mo4wdo9r_ow6fr6w6","truncated":false}} +{"action_id":"action_mo4wdzwn_79eqpma9","kind":"session.close","status":"started","started_at":"2026-04-18T22:17:31.655Z","input_summary":{"session_id":"session_mo4wclhc_lto1vdqi"},"session_id":"session_mo4wclhc_lto1vdqi"} +{"action_id":"action_mo4wdzwz_xtsqigvz","kind":"session.close","status":"started","started_at":"2026-04-18T22:17:31.667Z","input_summary":{"session_id":"session_mo4wd2gr_bd4gu6hs"},"session_id":"session_mo4wd2gr_bd4gu6hs"} +{"action_id":"action_mo4wdzwn_79eqpma9","kind":"session.close","status":"succeeded","started_at":"2026-04-18T22:17:31.655Z","input_summary":{"session_id":"session_mo4wclhc_lto1vdqi"},"session_id":"session_mo4wclhc_lto1vdqi","ended_at":"2026-04-18T22:17:31.695Z","result_summary":{"session_id":"session_mo4wclhc_lto1vdqi","closed":true,"status":"closed"}} +{"action_id":"action_mo4wdzwz_xtsqigvz","kind":"session.close","status":"succeeded","started_at":"2026-04-18T22:17:31.667Z","input_summary":{"session_id":"session_mo4wd2gr_bd4gu6hs"},"session_id":"session_mo4wd2gr_bd4gu6hs","ended_at":"2026-04-18T22:17:31.888Z","result_summary":{"session_id":"session_mo4wd2gr_bd4gu6hs","closed":true,"status":"closed"}} +{"action_id":"action_mo4wfctl_ui15zti8","kind":"session.create","status":"started","started_at":"2026-04-18T22:18:35.049Z","input_summary":{"profile_mode":"ephemeral","locale":"en-US","timezone_id":"UTC","user_data_dir":"","profile_directory":"","humanize":false,"launch_args":[],"viewport":{"width":390,"height":844}}} +{"action_id":"action_mo4wfctl_ui15zti8","kind":"session.create","status":"succeeded","started_at":"2026-04-18T22:18:35.049Z","input_summary":{"profile_mode":"ephemeral","locale":"en-US","timezone_id":"UTC","user_data_dir":"","profile_directory":"","humanize":false,"launch_args":[],"viewport":{"width":390,"height":844}},"ended_at":"2026-04-18T22:18:35.210Z","result_summary":{"session_id":"session_mo4wfctl_ayv0rkvy","context_id":"context_mo4wfcy2_axzvvgus","page_id":"page_mo4wfcy2_3nlfae17","status":"active","profile_mode":"ephemeral","locale":"en-US","timezone_id":"UTC","user_data_dir":"","profile_directory":"","humanize":false,"launch_args":[],"viewport":{"width":390,"height":844},"created_at":"2026-04-18T22:18:35.210Z","health":{"consecutive_errors":0,"restart_recommended":false},"capabilities":{"observe":true,"screenshot":true,"evaluate":true}}} +{"action_id":"action_mo4wfi80_mll0q44f","kind":"page.navigate","status":"started","started_at":"2026-04-18T22:18:42.048Z","input_summary":{"session_id":"session_mo4wfctl_ayv0rkvy","page_id":"page_mo4wfcy2_3nlfae17","url":"http://127.0.0.1:4173/","wait_until":"load"},"session_id":"session_mo4wfctl_ayv0rkvy","page_id":"page_mo4wfcy2_3nlfae17"} +{"action_id":"action_mo4wfi80_mll0q44f","kind":"page.navigate","status":"succeeded","started_at":"2026-04-18T22:18:42.048Z","input_summary":{"session_id":"session_mo4wfctl_ayv0rkvy","page_id":"page_mo4wfcy2_3nlfae17","url":"http://127.0.0.1:4173/","wait_until":"load"},"session_id":"session_mo4wfctl_ayv0rkvy","page_id":"page_mo4wfcy2_3nlfae17","ended_at":"2026-04-18T22:18:42.090Z","result_summary":{"page_id":"page_mo4wfcy2_3nlfae17","requested_url":"http://127.0.0.1:4173/","final_url":"http://127.0.0.1:4173/","title":"Luma Furniture","navigation_id":"action_mo4wfi96_3vjr78zx","timings":{"elapsed_ms":42},"follow_up_by_goal":{"interact":{"recommended_tool":"observe.a11y","screenshot_mode":"none","rationale":["Interaction tasks should start with structured page state, not screenshots."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]},"read":{"recommended_tool":"observe.text","screenshot_mode":"none","rationale":["Reading tasks should prefer cheap textual context before any visual artifact."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]},"visual_verify":{"recommended_tool":"observe.screenshot","screenshot_mode":"viewport","rationale":["Visual verification explicitly needs pixels, so screenshot escalation is justified."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]},"debug":{"recommended_tool":"observe.a11y","screenshot_mode":"none","rationale":["Debug flows usually need structural context before expensive screenshots.","A screenshot is useful only after structural context is insufficient."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]},"gesture":{"recommended_tool":"observe.boxes","screenshot_mode":"none","rationale":["Gesture execution needs precise spatial data before acting.","Bounding boxes are cheaper than screenshots and usually sufficient for coordinates."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]}}}} +{"action_id":"action_mo4wfi96_skmxw5ud","kind":"runtime.evaluate_js","status":"started","started_at":"2026-04-18T22:18:42.090Z","input_summary":{"session_id":"session_mo4wfctl_ayv0rkvy","page_id":"page_mo4wfcy2_3nlfae17","expression":"(() => ({ innerWidth: window.innerWidth, scrollWidth: document.documentElement.scrollWidth, hasHorizontalOverflow: document.documentElement.scrollWidth > window.innerWidth, heroCopyWidth: document.querySelector('.hero-copy')?.getBoundingClientRect().width, heroVisualWidth: document.querySelector('.hero-visual')?.getBoundingClientRect().width, pageShellWidth: document.querySelector('.page-shell')?.getBoundingClientRect().width }))()","await_promise":true},"session_id":"session_mo4wfctl_ayv0rkvy","page_id":"page_mo4wfcy2_3nlfae17"} +{"action_id":"action_mo4wfi96_skmxw5ud","kind":"runtime.evaluate_js","status":"succeeded","started_at":"2026-04-18T22:18:42.090Z","input_summary":{"session_id":"session_mo4wfctl_ayv0rkvy","page_id":"page_mo4wfcy2_3nlfae17","expression":"(() => ({ innerWidth: window.innerWidth, scrollWidth: document.documentElement.scrollWidth, hasHorizontalOverflow: document.documentElement.scrollWidth > window.innerWidth, heroCopyWidth: document.querySelector('.hero-copy')?.getBoundingClientRect().width, heroVisualWidth: document.querySelector('.hero-visual')?.getBoundingClientRect().width, pageShellWidth: document.querySelector('.page-shell')?.getBoundingClientRect().width }))()","await_promise":true},"session_id":"session_mo4wfctl_ayv0rkvy","page_id":"page_mo4wfcy2_3nlfae17","ended_at":"2026-04-18T22:18:42.094Z","result_summary":{"artifact_id":"artifact_mo4wfi9a_9uo2btyo","truncated":false}} +{"action_id":"action_mo4wfiad_ibikqcnu","kind":"observe.console","status":"started","started_at":"2026-04-18T22:18:42.133Z","input_summary":{"session_id":"session_mo4wfctl_ayv0rkvy","page_id":"page_mo4wfcy2_3nlfae17","limit":20},"session_id":"session_mo4wfctl_ayv0rkvy","page_id":"page_mo4wfcy2_3nlfae17"} +{"action_id":"action_mo4wfiad_ibikqcnu","kind":"observe.console","status":"succeeded","started_at":"2026-04-18T22:18:42.133Z","input_summary":{"session_id":"session_mo4wfctl_ayv0rkvy","page_id":"page_mo4wfcy2_3nlfae17","limit":20},"session_id":"session_mo4wfctl_ayv0rkvy","page_id":"page_mo4wfcy2_3nlfae17","ended_at":"2026-04-18T22:18:42.133Z","result_summary":{"artifact_id":"artifact_mo4wfiad_qdh56vqu","entry_count":0}} +{"action_id":"action_mo4wflwm_paz1h8hi","kind":"session.create","status":"started","started_at":"2026-04-18T22:18:46.822Z","input_summary":{"profile_mode":"ephemeral","locale":"en-US","timezone_id":"UTC","user_data_dir":"","profile_directory":"","humanize":false,"launch_args":[],"viewport":{"width":1440,"height":1100}}} +{"action_id":"action_mo4wflwm_paz1h8hi","kind":"session.create","status":"succeeded","started_at":"2026-04-18T22:18:46.822Z","input_summary":{"profile_mode":"ephemeral","locale":"en-US","timezone_id":"UTC","user_data_dir":"","profile_directory":"","humanize":false,"launch_args":[],"viewport":{"width":1440,"height":1100}},"ended_at":"2026-04-18T22:18:46.975Z","result_summary":{"session_id":"session_mo4wflwn_16vwiof3","context_id":"context_mo4wfm0v_uokzqswb","page_id":"page_mo4wfm0v_zi6qkdwb","status":"active","profile_mode":"ephemeral","locale":"en-US","timezone_id":"UTC","user_data_dir":"","profile_directory":"","humanize":false,"launch_args":[],"viewport":{"width":1440,"height":1100},"created_at":"2026-04-18T22:18:46.975Z","health":{"consecutive_errors":0,"restart_recommended":false},"capabilities":{"observe":true,"screenshot":true,"evaluate":true}}} +{"action_id":"action_mo4wfqkh_cbrefpei","kind":"page.navigate","status":"started","started_at":"2026-04-18T22:18:52.865Z","input_summary":{"session_id":"session_mo4wflwn_16vwiof3","page_id":"page_mo4wfm0v_zi6qkdwb","url":"http://127.0.0.1:4173/","wait_until":"load"},"session_id":"session_mo4wflwn_16vwiof3","page_id":"page_mo4wfm0v_zi6qkdwb"} +{"action_id":"action_mo4wfqkh_cbrefpei","kind":"page.navigate","status":"succeeded","started_at":"2026-04-18T22:18:52.865Z","input_summary":{"session_id":"session_mo4wflwn_16vwiof3","page_id":"page_mo4wfm0v_zi6qkdwb","url":"http://127.0.0.1:4173/","wait_until":"load"},"session_id":"session_mo4wflwn_16vwiof3","page_id":"page_mo4wfm0v_zi6qkdwb","ended_at":"2026-04-18T22:18:52.905Z","result_summary":{"page_id":"page_mo4wfm0v_zi6qkdwb","requested_url":"http://127.0.0.1:4173/","final_url":"http://127.0.0.1:4173/","title":"Luma Furniture","navigation_id":"action_mo4wfqll_e6wg01xx","timings":{"elapsed_ms":40},"follow_up_by_goal":{"interact":{"recommended_tool":"observe.a11y","screenshot_mode":"none","rationale":["Interaction tasks should start with structured page state, not screenshots."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]},"read":{"recommended_tool":"observe.text","screenshot_mode":"none","rationale":["Reading tasks should prefer cheap textual context before any visual artifact."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]},"visual_verify":{"recommended_tool":"observe.screenshot","screenshot_mode":"viewport","rationale":["Visual verification explicitly needs pixels, so screenshot escalation is justified."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]},"debug":{"recommended_tool":"observe.a11y","screenshot_mode":"none","rationale":["Debug flows usually need structural context before expensive screenshots.","A screenshot is useful only after structural context is insufficient."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]},"gesture":{"recommended_tool":"observe.boxes","screenshot_mode":"none","rationale":["Gesture execution needs precise spatial data before acting.","Bounding boxes are cheaper than screenshots and usually sufficient for coordinates."],"escalation_order":["observe.a11y","observe.dom","observe.text","observe.boxes","observe.screenshot"]}}}} +{"action_id":"action_mo4wfqm2_z30ieemu","kind":"runtime.evaluate_js","status":"started","started_at":"2026-04-18T22:18:52.922Z","input_summary":{"session_id":"session_mo4wflwn_16vwiof3","page_id":"page_mo4wfm0v_zi6qkdwb","expression":"(() => ({ innerWidth: window.innerWidth, scrollWidth: document.documentElement.scrollWidth, hasHorizontalOverflow: document.documentElement.scrollWidth > window.innerWidth }))()","await_promise":true},"session_id":"session_mo4wflwn_16vwiof3","page_id":"page_mo4wfm0v_zi6qkdwb"} +{"action_id":"action_mo4wfqm2_lei4ios9","kind":"observe.console","status":"started","started_at":"2026-04-18T22:18:52.922Z","input_summary":{"session_id":"session_mo4wflwn_16vwiof3","page_id":"page_mo4wfm0v_zi6qkdwb","limit":20},"session_id":"session_mo4wflwn_16vwiof3","page_id":"page_mo4wfm0v_zi6qkdwb"} +{"action_id":"action_mo4wfqm2_lei4ios9","kind":"observe.console","status":"succeeded","started_at":"2026-04-18T22:18:52.922Z","input_summary":{"session_id":"session_mo4wflwn_16vwiof3","page_id":"page_mo4wfm0v_zi6qkdwb","limit":20},"session_id":"session_mo4wflwn_16vwiof3","page_id":"page_mo4wfm0v_zi6qkdwb","ended_at":"2026-04-18T22:18:52.923Z","result_summary":{"artifact_id":"artifact_mo4wfqm2_q2xwfxa2","entry_count":0}} +{"action_id":"action_mo4wfqm2_z30ieemu","kind":"runtime.evaluate_js","status":"succeeded","started_at":"2026-04-18T22:18:52.922Z","input_summary":{"session_id":"session_mo4wflwn_16vwiof3","page_id":"page_mo4wfm0v_zi6qkdwb","expression":"(() => ({ innerWidth: window.innerWidth, scrollWidth: document.documentElement.scrollWidth, hasHorizontalOverflow: document.documentElement.scrollWidth > window.innerWidth }))()","await_promise":true},"session_id":"session_mo4wflwn_16vwiof3","page_id":"page_mo4wfm0v_zi6qkdwb","ended_at":"2026-04-18T22:18:52.924Z","result_summary":{"artifact_id":"artifact_mo4wfqm3_x2ai16as","truncated":false}} +{"action_id":"action_mo4wfthz_59cwfy9x","kind":"session.close","status":"started","started_at":"2026-04-18T22:18:56.663Z","input_summary":{"session_id":"session_mo4wfctl_ayv0rkvy"},"session_id":"session_mo4wfctl_ayv0rkvy"} +{"action_id":"action_mo4wfti0_hbshmwov","kind":"session.close","status":"started","started_at":"2026-04-18T22:18:56.664Z","input_summary":{"session_id":"session_mo4wflwn_16vwiof3"},"session_id":"session_mo4wflwn_16vwiof3"} +{"action_id":"action_mo4wfti0_hbshmwov","kind":"session.close","status":"succeeded","started_at":"2026-04-18T22:18:56.664Z","input_summary":{"session_id":"session_mo4wflwn_16vwiof3"},"session_id":"session_mo4wflwn_16vwiof3","ended_at":"2026-04-18T22:18:56.718Z","result_summary":{"session_id":"session_mo4wflwn_16vwiof3","closed":true,"status":"closed"}} +{"action_id":"action_mo4wfthz_59cwfy9x","kind":"session.close","status":"succeeded","started_at":"2026-04-18T22:18:56.663Z","input_summary":{"session_id":"session_mo4wfctl_ayv0rkvy"},"session_id":"session_mo4wfctl_ayv0rkvy","ended_at":"2026-04-18T22:18:56.837Z","result_summary":{"session_id":"session_mo4wfctl_ayv0rkvy","closed":true,"status":"closed"}} diff --git a/.data/history/artifacts.jsonl b/.data/history/artifacts.jsonl new file mode 100644 index 0000000..b20ad3d --- /dev/null +++ b/.data/history/artifacts.jsonl @@ -0,0 +1,13 @@ +{"artifact_id":"artifact_mo4wctmm_ltj99rgk","kind":"markdown","created_at":"2026-04-18T22:16:36.862Z","storage_path":"/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wctmm_ltj99rgk.txt","bytes":3416,"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","action_id":"action_mo4wctmj_tvms4x1c","url":"http://127.0.0.1:4173/","title":"Luma Furniture","meta":{"requested_format":"markdown","truncated":false}} +{"artifact_id":"artifact_mo4wctmo_xv8olpy5","kind":"console","created_at":"2026-04-18T22:16:36.864Z","storage_path":"/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wctmo_xv8olpy5.txt","bytes":292,"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","action_id":"action_mo4wctmo_kf3373mj","url":"http://127.0.0.1:4173/","title":"Luma Furniture","meta":{"limit":50,"entry_count":1}} +{"artifact_id":"artifact_mo4wctms_xayl488b","kind":"dom","created_at":"2026-04-18T22:16:36.868Z","storage_path":"/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wctms_xayl488b.txt","bytes":946,"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","action_id":"action_mo4wctmr_vvu0bopv","url":"http://127.0.0.1:4173/","title":"Luma Furniture"} +{"artifact_id":"artifact_mo4wd026_g4ll3ev3","kind":"eval","created_at":"2026-04-18T22:16:45.198Z","storage_path":"/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wd026_g4ll3ev3.txt","bytes":137,"session_id":"session_mo4wclhc_lto1vdqi","page_id":"page_mo4wcltv_o5uyk2mr","action_id":"action_mo4wd023_k49m4fm4","url":"http://127.0.0.1:4173/","title":"Luma Furniture","meta":{"await_promise":true,"inline":true}} +{"artifact_id":"artifact_mo4wd8km_b06quyjz","kind":"eval","created_at":"2026-04-18T22:16:56.230Z","storage_path":"/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wd8km_b06quyjz.txt","bytes":163,"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","action_id":"action_mo4wd8ki_kn4o27zi","url":"http://127.0.0.1:4173/","title":"Luma Furniture","meta":{"await_promise":true,"inline":true}} +{"artifact_id":"artifact_mo4wd8lh_61mzru65","kind":"console","created_at":"2026-04-18T22:16:56.261Z","storage_path":"/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wd8lh_61mzru65.txt","bytes":292,"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","action_id":"action_mo4wd8lh_26192ual","url":"http://127.0.0.1:4173/","title":"Luma Furniture","meta":{"limit":20,"entry_count":1}} +{"artifact_id":"artifact_mo4wddlx_7t6jxcyd","kind":"eval","created_at":"2026-04-18T22:17:02.757Z","storage_path":"/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wddlx_7t6jxcyd.txt","bytes":366,"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","action_id":"action_mo4wddlu_5qh277ag","url":"http://127.0.0.1:4173/","title":"Luma Furniture","meta":{"await_promise":true,"inline":true}} +{"artifact_id":"artifact_mo4wdio2_2qoatfwk","kind":"eval","created_at":"2026-04-18T22:17:09.314Z","storage_path":"/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wdio2_2qoatfwk.txt","bytes":1469,"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","action_id":"action_mo4wdinz_44rfbhf4","url":"http://127.0.0.1:4173/","title":"Luma Furniture","meta":{"await_promise":true,"inline":true}} +{"artifact_id":"artifact_mo4wdo9r_ow6fr6w6","kind":"eval","created_at":"2026-04-18T22:17:16.575Z","storage_path":"/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wdo9r_ow6fr6w6.txt","bytes":2471,"session_id":"session_mo4wd2gr_bd4gu6hs","page_id":"page_mo4wd2lg_383zitmf","action_id":"action_mo4wdo9o_jthvr182","url":"http://127.0.0.1:4173/","title":"Luma Furniture","meta":{"await_promise":true,"inline":true}} +{"artifact_id":"artifact_mo4wfi9a_9uo2btyo","kind":"eval","created_at":"2026-04-18T22:18:42.094Z","storage_path":"/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wfi9a_9uo2btyo.txt","bytes":154,"session_id":"session_mo4wfctl_ayv0rkvy","page_id":"page_mo4wfcy2_3nlfae17","action_id":"action_mo4wfi96_skmxw5ud","url":"http://127.0.0.1:4173/","title":"Luma Furniture","meta":{"await_promise":true,"inline":true}} +{"artifact_id":"artifact_mo4wfiad_qdh56vqu","kind":"console","created_at":"2026-04-18T22:18:42.133Z","storage_path":"/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wfiad_qdh56vqu.txt","bytes":2,"session_id":"session_mo4wfctl_ayv0rkvy","page_id":"page_mo4wfcy2_3nlfae17","action_id":"action_mo4wfiad_ibikqcnu","url":"http://127.0.0.1:4173/","title":"Luma Furniture","meta":{"limit":20,"entry_count":0}} +{"artifact_id":"artifact_mo4wfqm2_q2xwfxa2","kind":"console","created_at":"2026-04-18T22:18:52.923Z","storage_path":"/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wfqm2_q2xwfxa2.txt","bytes":2,"session_id":"session_mo4wflwn_16vwiof3","page_id":"page_mo4wfm0v_zi6qkdwb","action_id":"action_mo4wfqm2_lei4ios9","url":"http://127.0.0.1:4173/","title":"Luma Furniture","meta":{"limit":20,"entry_count":0}} +{"artifact_id":"artifact_mo4wfqm3_x2ai16as","kind":"eval","created_at":"2026-04-18T22:18:52.924Z","storage_path":"/home/cemalturkcan/code/opencode-pair/.data/artifacts/artifact_mo4wfqm3_x2ai16as.txt","bytes":81,"session_id":"session_mo4wflwn_16vwiof3","page_id":"page_mo4wfm0v_zi6qkdwb","action_id":"action_mo4wfqm2_z30ieemu","url":"http://127.0.0.1:4173/","title":"Luma Furniture","meta":{"await_promise":true,"inline":true}} diff --git a/.opencode/package-lock.json b/.opencode/package-lock.json index 6aa38cb..ae941df 100644 --- a/.opencode/package-lock.json +++ b/.opencode/package-lock.json @@ -5,21 +5,100 @@ "packages": { "": { "dependencies": { - "@opencode-ai/plugin": "1.3.15" + "@opencode-ai/plugin": "1.4.9" } }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@opencode-ai/plugin": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.3.15.tgz", - "integrity": "sha512-jZJbuvUXc5Limz8pacQl+ffATjjKGlq+xaA4wTUeW+/spwOf7Yr5Ryyvan8eNlYM8wy6h5SLfznl1rlFpjYC8w==", + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.4.9.tgz", + "integrity": "sha512-tUtPbPs5xP9wonwuz5d/2y8QTrqFR8HOtAVTXvZ6iG26NJfW0dnnw9oTusVOayEIemd5abytCESm7X9ZZOMftQ==", "license": "MIT", "dependencies": { - "@opencode-ai/sdk": "1.3.15", + "@opencode-ai/sdk": "1.4.9", + "effect": "4.0.0-beta.48", "zod": "4.1.8" }, "peerDependencies": { - "@opentui/core": ">=0.1.96", - "@opentui/solid": ">=0.1.96" + "@opentui/core": ">=0.1.100", + "@opentui/solid": ">=0.1.100" }, "peerDependenciesMeta": { "@opentui/core": { @@ -31,14 +110,20 @@ } }, "node_modules/@opencode-ai/sdk": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.3.15.tgz", - "integrity": "sha512-Uk59C7wsK20wpdr277yx7Xz7TqG5jGqlZUpSW3wDH/7a2K2iBg0lXc2wskHuCXLRXMhXpPZtb4a3SOpPENkkbg==", + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.4.9.tgz", + "integrity": "sha512-S8WQLuBFu2WwvSc1wupsV4qskniBA+JN1VaZZs52BPWwiN2zQFTD5/6dMh6oiYOMDtPjKsTFZ6qLFxDvVPNggQ==", "license": "MIT", "dependencies": { "cross-spawn": "7.0.6" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -53,12 +138,135 @@ "node": ">= 8" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/effect": { + "version": "4.0.0-beta.48", + "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-beta.48.tgz", + "integrity": "sha512-MMAM/ZabuNdNmgXiin+BAanQXK7qM8mlt7nfXDoJ/Gn9V8i89JlCq+2N0AiWmqFLXjGLA0u3FjiOjSOYQk5uMw==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "fast-check": "^4.6.0", + "find-my-way-ts": "^0.1.6", + "ini": "^6.0.0", + "kubernetes-types": "^1.30.0", + "msgpackr": "^1.11.9", + "multipasta": "^0.2.7", + "toml": "^4.1.1", + "uuid": "^13.0.0", + "yaml": "^2.8.3" + } + }, + "node_modules/fast-check": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.7.0.tgz", + "integrity": "sha512-NsZRtqvSSoCP0HbNjUD+r1JH8zqZalyp6gLY9e7OYs7NK9b6AHOs2baBFeBG7bVNsuoukh89x2Yg3rPsul8ziQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^8.0.0" + }, + "engines": { + "node": ">=12.17.0" + } + }, + "node_modules/find-my-way-ts": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/find-my-way-ts/-/find-my-way-ts-0.1.6.tgz", + "integrity": "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==", + "license": "MIT" + }, + "node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/kubernetes-types": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/kubernetes-types/-/kubernetes-types-1.30.0.tgz", + "integrity": "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==", + "license": "Apache-2.0" + }, + "node_modules/msgpackr": { + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.9.tgz", + "integrity": "sha512-FkoAAyyA6HM8wL882EcEyFZ9s7hVADSwG9xrVx3dxxNQAtgADTrJoEWivID82Iv1zWDsv/OtbrrcZAzGzOMdNw==", + "license": "MIT", + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, + "node_modules/multipasta": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/multipasta/-/multipasta-0.2.7.tgz", + "integrity": "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==", + "license": "MIT" + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -68,6 +276,22 @@ "node": ">=8" } }, + "node_modules/pure-rand": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.0.tgz", + "integrity": "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -89,6 +313,28 @@ "node": ">=8" } }, + "node_modules/toml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/toml/-/toml-4.1.1.tgz", + "integrity": "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -104,6 +350,21 @@ "node": ">= 8" } }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/zod": { "version": "4.1.8", "license": "MIT", diff --git a/README.md b/README.md index 9f32508..fa90cc5 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,33 @@ # opencode-pair -OpenCode harness with a three-agent setup: one primary, one general subagent, one validation-focused subagent. +OpenCode harness with a six-agent setup: two primaries, one general subagent, one ideation subagent, one frontend design subagent, and one validation-focused subagent. ## What it does - **MrRobot** is the primary agent. He routes work and answers plainly. +- **Wick** is the fast primary executor. He handles narrow, concrete tasks with minimal overhead. - **Eliot** is the general subagent. He handles implementation, refactors, repo exploration, and other scoped task work. -- **Validator** is the validation-focused subagent. It reviews changes again after implementation and can also execute work when routed. +- **Tyrell** is the ideation subagent. It handles brainstorming, naming, UX direction, product ideas, and open-ended exploratory packets. +- **Michelangelo** is the frontend design subagent. He is the default implementation lane for pages, components, styling, layout, and visual polish unless the user explicitly asks for review-only output or no file edits. +- Implementation packets should be edited directly in the repo by the assigned subagent; research, review, and ideation packets should return findings without edits unless edits are explicitly requested. +- Ongoing subagent work should continue with the same `task_id` by default when the lane and workstream still match. +- **Turing** is the validation-focused subagent. - No plan/execute mode or harness slash-command flow. -- No session memory, pattern learning, observation logs, or cross-session state injection. +- No cross-session memory, pattern learning, or observation logs. Workflow-local task tracking only keeps recent `task_id` hints for continuation inside related sessions. - Comment guard blocks suspicious AI-style comments before file writes and surfaces anything that still slips through. ## Agents | Agent | Character | Role | Model | | ----- | --------- | ---- | ----- | -| **mrrobot** | Mr. Robot | Primary agent — routes, synthesizes, answers | openai/gpt-5.4-fast | -| **eliot** | Elliot | General-purpose subagent | openai/gpt-5.4-fast | -| **validator** | Validator | Validation-focused review and verification | openai/gpt-5.4-fast | +| **mrrobot** | MrRobot | Primary agent — routes, synthesizes, answers | openai/gpt-5.4 | +| **wick** | Wick | Primary fast executor — finishes narrow tasks directly | openai/gpt-5.4-mini | +| **eliot** | Eliot | General-purpose subagent | openai/gpt-5.4-fast | +| **tyrell** | Tyrell | Ideation-focused subagent | openai/gpt-5.4-fast | +| **michelangelo** | Michelangelo | Frontend design subagent | google-custom/google-custom-gemini-3.1-pro | +| **turing** | Turing | Validation-focused review and verification | openai/gpt-5.4-fast | -All three use the `high` variant. +MrRobot uses the `xhigh` variant. Eliot, Tyrell, Turing, and Michelangelo use the `high` variant. Wick uses the `low` variant for lower-latency execution. ## MCP Servers @@ -35,7 +43,7 @@ All three use the `high` variant. Shared managed MCP roots stay under `~/.config/{mcp_name}`. -All three agents receive the same enabled MCP set and the same default full tool access. The harness does not add per-agent MCP or tool restrictions. +All six agents receive the same enabled MCP set and the same default full tool access. The harness does not add per-agent MCP or tool restrictions. ## Prerequisites @@ -48,7 +56,7 @@ bunx opencode-pair install ``` The installer will: -1. Wire agents and MCPs into OpenCode config +1. Wire agents, the Google (custom) plugin, and MCPs into OpenCode config 2. Install shell strategy instructions 3. Vendor `pg-mcp`, `ssh-mcp`, `web-agent-mcp`, and bundled skills 4. Install dependencies inside each shared managed MCP root @@ -74,6 +82,8 @@ opencode-pair init # create project-local config opencode-pair print-config # inspect generated config ``` +`install` also adds `opencode-google-login@latest`, so you do not need a separate local plugin path for the Google (custom) provider. + ## Config Merges from two layers (project wins): @@ -93,11 +103,13 @@ opencode-pair init | Hook | What it does | | ---- | ------------ | -| `chat.message` | Inject project docs and WSL notes for MrRobot; inject compact project facts for subagents | +| `chat.message` | Inject project docs, WSL notes, and active subagent task IDs for MrRobot and Wick; inject compact project facts for Eliot, Tyrell, Michelangelo, and Turing | | `tool.execute.before` | Block suspicious AI-style comments before writes, enforce git-push build gate, auto-transform Node commands on WSL | -| `tool.execute.after` | Surface suspicious comments that still remain after a write | +| `tool.execute.after` | Surface suspicious comments that still remain after a write; capture subagent task IDs for continuation hints | | `session.deleted` | Clear ephemeral runtime state | +`hooks.task_tracking` defaults to `true` and controls task-id capture plus primary-session continuation hints. + ## Architecture ``` @@ -105,8 +117,8 @@ src/ ├── prompts/ │ ├── mcp-access.ts # Enabled MCP list and prompt guidance │ ├── shared.ts # Shared prompt rules and response style -│ ├── workers.ts # Eliot + validator prompt builders -│ └── coordinator.ts # MrRobot prompt and routing rules +│ ├── workers.ts # Eliot, Tyrell, Michelangelo, and Turing prompt builders +│ └── coordinator.ts # MrRobot and Wick prompt builders plus routing rules ├── agents.ts # Agent definitions (models and prompts) ├── mcp.ts # MCP server registration ├── hooks/ # Runtime hooks (comment guard, WSL, cleanup) diff --git a/docs/agent-matrix.md b/docs/agent-matrix.md index 6a6319f..e315c59 100644 --- a/docs/agent-matrix.md +++ b/docs/agent-matrix.md @@ -4,9 +4,12 @@ | Agent | Mode | Character | Model | Variant | Role | | ----- | ---- | --------- | ----- | ------- | ---- | -| `mrrobot` | `primary` | Mr. Robot | `openai/gpt-5.4-fast` | `high` | Primary agent. Routes work, synthesizes, and gives the final answer. | -| `eliot` | `subagent` | Elliot | `openai/gpt-5.4-fast` | `high` | General subagent for implementation, refactors, repo exploration, and scoped execution. | -| `validator` | `subagent` | Validator | `openai/gpt-5.4-fast` | `high` | Validation-focused pass. Reviews diffs, runs checks, and returns approve/request-changes. | +| `mrrobot` | `primary` | MrRobot | `openai/gpt-5.4` | `xhigh` | Primary agent. Routes work, synthesizes, and gives the final answer. | +| `wick` | `primary` | Wick | `openai/gpt-5.4-mini` | `low` | Fast primary executor. Finishes narrow, concrete tasks directly with minimal overhead. | +| `eliot` | `subagent` | Eliot | `openai/gpt-5.4-fast` | `high` | General subagent for implementation, refactors, repo exploration, and scoped execution. | +| `tyrell` | `subagent` | Tyrell | `openai/gpt-5.4-fast` | `high` | Ideation-focused subagent for brainstorming, naming, UX direction, and exploratory packets. | +| `michelangelo` | `subagent` | Michelangelo | `google-custom/google-custom-gemini-3.1-pro` | `high` | Frontend design subagent. Owns layout, styling, visual hierarchy, responsive UX, and UI polish. | +| `turing` | `subagent` | Turing | `openai/gpt-5.4-fast` | `high` | Validation-focused pass. Reviews diffs, runs checks, and returns approve/request-changes. | ## MCP Model @@ -21,18 +24,25 @@ Defined in `src/prompts/mcp-access.ts`. | Agent | Can spawn subagents? | Can edit files? | Can use bash? | Notes | | ----- | -------------------- | --------------- | -------------- | ----- | | **mrrobot** | yes | yes | yes | Primary agent. Uses real OpenCode `primary` mode. | +| **wick** | yes | yes | yes | Primary fast executor. Uses real OpenCode `primary` mode. | | **eliot** | yes | yes | yes | General-purpose subagent. Uses real OpenCode `subagent` mode. | -| **validator** | yes | yes | yes | Validation-focused subagent. Review behavior comes from prompt/persona, not harness restrictions. | +| **tyrell** | yes | yes | yes | Ideation-focused subagent. Uses real OpenCode `subagent` mode. | +| **michelangelo** | yes | yes | yes | Frontend design subagent. Frontend-only behavior comes from prompt/persona, not harness restrictions. | +| **turing** | yes | yes | yes | Validation-focused subagent with the Turing persona. Review behavior comes from prompt/persona, not harness restrictions. | The harness does not add per-agent MCP or tool restrictions. There is no delegate lane and no background-agent flow. All subagent work goes through OpenCode Task semantics. ## Workflow 1. Inspect the repo and shape the packet. -2. Route implementation to `eliot` when delegation is useful. -3. Run a `validator` pass after implementation for any non-trivial change. -4. If `validator` requests changes, send the fix back to `eliot`, then run `validator` again. -5. Stop after two repair cycles if risk remains unresolved. +2. Use `wick` when the user manually selects the fast execution lane for narrow, concrete work. +3. Mark delegated packets as implementation, research, review, or ideation. +4. Route implementation to `eliot` when delegation is useful, and have Eliot edit the repo directly unless the packet is explicitly review-only or no-file-edit. +5. Route frontend design, layout, styling, and UI polish work to `michelangelo` by default unless the user explicitly asks for review-only output or no file edits. +6. Reuse the same subagent `task_id` by default when the lane and workstream are continuing. +7. Run a `turing` pass after implementation for any non-trivial change. +8. If `turing` requests changes, send the fix back to the implementation lane, then run `turing` again. +9. Stop after two repair cycles if risk remains unresolved. There is no plan mode, `/go`, `/plan`, or `/execute` harness flow. diff --git a/examples/opencode-pair-autonomy.jsonc b/examples/opencode-pair-autonomy.jsonc index 2f73f6f..be7151e 100644 --- a/examples/opencode-pair-autonomy.jsonc +++ b/examples/opencode-pair-autonomy.jsonc @@ -5,6 +5,7 @@ "comment_guard": true, "session_start": true, "pre_tool_use": true, + "task_tracking": true, "post_tool_use": true, "pre_compact": true, "stop": true, diff --git a/examples/opencode.jsonc b/examples/opencode.jsonc index a1da04a..b0d4c6a 100644 --- a/examples/opencode.jsonc +++ b/examples/opencode.jsonc @@ -2,11 +2,11 @@ // Reference example for the plugin stack after installation. "plugin": [ "opencode-pair", + "opencode-google-login@latest", "opencode-pty", "@mohak34/opencode-notifier", "@zenobius/opencode-skillful", - "@franlol/opencode-md-table-formatter", - "file:///ABSOLUTE/PATH/TO/opencode-background-agents-local" + "@franlol/opencode-md-table-formatter" ], "instructions": [ "~/.config/opencode/plugin/shell-strategy/shell_strategy.md" diff --git a/package.json b/package.json index 10f1ca8..2bbf51b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "opencode-pair", "version": "0.1.0", - "description": "OpenCode harness with a three-agent topology: mrrobot, eliot, validator.", + "description": "OpenCode harness with a six-agent topology: mrrobot, wick, eliot, tyrell, michelangelo, turing.", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/__tests__/agents.test.ts b/src/__tests__/agents.test.ts index 8d86386..d8e74c4 100644 --- a/src/__tests__/agents.test.ts +++ b/src/__tests__/agents.test.ts @@ -1,22 +1,32 @@ import { describe, expect, it } from "bun:test"; import { createHarnessAgents } from "../agents"; import { createHarnessCommands } from "../commands"; -import { buildCoordinatorPrompt } from "../prompts/coordinator"; +import { createSessionStartHook } from "../hooks/session-start"; +import { createTaskTrackingHook } from "../hooks/task-tracking"; +import { PRIMARY_AGENTS } from "../hooks/runtime"; +import { createHookRuntime } from "../hooks/runtime"; +import { buildCoordinatorPrompt, buildWickPrompt } from "../prompts/coordinator"; import { DEFAULT_SKILL_SHORTLIST_TEXT } from "../prompts/shared"; -import { buildEliotPrompt, buildTyrellPrompt } from "../prompts/workers"; +import { + buildEliotPrompt, + buildMichelangeloPrompt, + buildTyrellPrompt, +} from "../prompts/workers"; import { getEnabledMcps } from "../prompts/mcp-access"; describe("createHarnessAgents", () => { - it("registers mrrobot, eliot, tyrell, and validator plus disabled built-ins", () => { + it("registers mrrobot, wick, eliot, tyrell, michelangelo, and turing plus disabled built-ins", () => { const agents = createHarnessAgents({ agents: {}, mcps: {} }); expect(Object.keys(agents).sort()).toEqual([ "build", "eliot", + "michelangelo", "mrrobot", "plan", + "turing", "tyrell", - "validator", + "wick", ]); }); @@ -24,12 +34,35 @@ describe("createHarnessAgents", () => { const agents = createHarnessAgents({ agents: {}, mcps: {} }); const mrrobot = agents.mrrobot as { mode: string; + model: string; variant: string; permission?: unknown; tools?: unknown; }; - const eliot = agents.eliot as { permission?: unknown; tools?: unknown }; + const wick = agents.wick as { + mode: string; + model: string; + variant: string; + temperature?: unknown; + permission?: unknown; + tools?: unknown; + }; + const eliot = agents.eliot as { + model: string; + permission?: unknown; + tools?: unknown; + }; + const michelangelo = agents.michelangelo as { + mode: string; + model: string; + variant?: unknown; + temperature?: unknown; + hidden?: unknown; + permission?: unknown; + tools?: unknown; + }; const tyrell = agents.tyrell as { + model: string; mode: string; variant: string; temperature?: unknown; @@ -37,7 +70,8 @@ describe("createHarnessAgents", () => { permission?: unknown; tools?: unknown; }; - const validator = agents.validator as { + const turing = agents.turing as { + model: string; mode: string; variant: string; permission?: unknown; @@ -45,21 +79,44 @@ describe("createHarnessAgents", () => { }; expect(mrrobot.mode).toBe("primary"); - expect(mrrobot.variant).toBe("high"); + expect(mrrobot.model).toBe("openai/gpt-5.4"); + expect(mrrobot.variant).toBe("xhigh"); expect(mrrobot.permission).toBeUndefined(); expect(mrrobot.tools).toBeUndefined(); + expect(wick.mode).toBe("primary"); + expect(wick.model).toBe("openai/gpt-5.4-mini"); + expect(wick.variant).toBe("low"); + expect(wick.temperature).toBe(0.0); + expect(wick.permission).toBeUndefined(); + expect(wick.tools).toBeUndefined(); + expect(eliot.model).toBe("openai/gpt-5.4-fast"); expect(eliot.permission).toBeUndefined(); expect(eliot.tools).toBeUndefined(); + expect(michelangelo.mode).toBe("subagent"); + expect(michelangelo.model).toBe("google-custom/google-custom-gemini-3.1-pro"); + expect(michelangelo.variant).toBe("high"); + expect(michelangelo.temperature).toBe(0.4); + expect(michelangelo.hidden).toBe(true); + expect(michelangelo.permission).toBeUndefined(); + expect(michelangelo.tools).toBeUndefined(); expect(tyrell.mode).toBe("subagent"); + expect(tyrell.model).toBe("openai/gpt-5.4-fast"); expect(tyrell.variant).toBe("high"); expect(tyrell.temperature).toBe(0.7); expect(tyrell.hidden).toBe(true); expect(tyrell.permission).toBeUndefined(); expect(tyrell.tools).toBeUndefined(); - expect(validator.mode).toBe("subagent"); - expect(validator.variant).toBe("high"); - expect(validator.permission).toBeUndefined(); - expect(validator.tools).toBeUndefined(); + expect(turing.mode).toBe("subagent"); + expect(turing.model).toBe("openai/gpt-5.4-fast"); + expect(turing.variant).toBe("high"); + expect(turing.permission).toBeUndefined(); + expect(turing.tools).toBeUndefined(); + }); + + it("treats mrrobot and wick as primary agents for session injection", () => { + expect(PRIMARY_AGENTS.has("mrrobot")).toBe(true); + expect(PRIMARY_AGENTS.has("wick")).toBe(true); + expect(PRIMARY_AGENTS.has("eliot")).toBe(false); }); }); @@ -93,13 +150,207 @@ describe("createHarnessCommands", () => { }); }); +describe("session start hook", () => { + it("gives Wick primary injection and Eliot compact subagent context", async () => { + const ctx = { directory: process.cwd() } as any; + const runtime = createHookRuntime(ctx, { workflow: { compact_subagent_context: true } }); + const hook = createSessionStartHook( + ctx, + { workflow: { compact_subagent_context: true } }, + runtime, + ); + + const wickOutput: { message: Record } = { + message: { agent: "wick" }, + }; + await hook["chat.message"]?.({ sessionID: "s1", agent: "wick" }, wickOutput); + expect(String(wickOutput.message.system ?? "")).toContain( + "[ProjectDocs] Available: README.md.", + ); + expect(String(wickOutput.message.system ?? "")).not.toContain("[ProjectContext]"); + + const eliotOutput: { message: Record } = { + message: { agent: "eliot" }, + }; + await hook["chat.message"]?.({ sessionID: "s2", agent: "eliot" }, eliotOutput); + expect(String(eliotOutput.message.system ?? "")).toContain("[ProjectContext]"); + expect(String(eliotOutput.message.system ?? "")).not.toContain( + "[ProjectDocs] Available: README.md.", + ); + }); + + it("injects active subagent task ids into primary sessions without collapsing same-lane threads", async () => { + const ctx = { directory: process.cwd() } as any; + const runtime = createHookRuntime(ctx, { workflow: { compact_subagent_context: true } }); + const sessionStartHook = createSessionStartHook( + ctx, + { workflow: { compact_subagent_context: true } }, + runtime, + ); + const taskTrackingHook = createTaskTrackingHook(runtime); + + await taskTrackingHook["tool.execute.after"]?.( + { + sessionID: "s3", + tool: "task", + args: { + subagent_type: "michelangelo", + description: "Refine landing page", + }, + }, + { + output: '{"task_id":"task_123abc"}', + }, + ); + await taskTrackingHook["tool.execute.after"]?.( + { + sessionID: "s3", + tool: "task", + args: { + subagent_type: "michelangelo", + description: "Polish checkout hero", + }, + }, + { + output: '{"task_id":"task_456def"}', + }, + ); + await taskTrackingHook["tool.execute.after"]?.( + { + sessionID: "s3", + tool: "task", + args: { + subagent_type: "michelangelo", + }, + }, + { + output: '{"task_id":"task_789ghi"}', + }, + ); + + const wickOutput: { message: Record } = { + message: { agent: "wick" }, + }; + await sessionStartHook["chat.message"]?.( + { sessionID: "s3", agent: "wick" }, + wickOutput, + ); + + expect(String(wickOutput.message.system ?? "")).toContain("[SubagentTasks]"); + expect(String(wickOutput.message.system ?? "")).toContain( + "michelangelo=task_123abc (Refine landing page)", + ); + expect(String(wickOutput.message.system ?? "")).toContain( + "michelangelo=task_456def (Polish checkout hero)", + ); + expect(String(wickOutput.message.system ?? "")).toContain( + "michelangelo=task_789ghi", + ); + }); + + it("injects active subagent task ids into subagent sessions alongside compact context", async () => { + const ctx = { directory: process.cwd() } as any; + const runtime = createHookRuntime(ctx, { workflow: { compact_subagent_context: true } }); + const sessionStartHook = createSessionStartHook( + ctx, + { workflow: { compact_subagent_context: true } }, + runtime, + ); + const taskTrackingHook = createTaskTrackingHook(runtime); + + await taskTrackingHook["tool.execute.after"]?.( + { + sessionID: "s4", + tool: "task", + args: { + subagent_type: "turing", + description: "Review latest diff", + }, + }, + { + output: '{"task_id":"task_turing001","sessionID":"child-turing"}', + }, + ); + + const eliotOutput: { message: Record } = { + message: { agent: "eliot" }, + }; + await sessionStartHook["chat.message"]?.( + { sessionID: "child-turing", agent: "eliot" }, + eliotOutput, + ); + + expect(String(eliotOutput.message.system ?? "")).toContain("[ProjectContext]"); + expect(String(eliotOutput.message.system ?? "")).toContain("[SubagentTasks]"); + expect(String(eliotOutput.message.system ?? "")).toContain( + "turing=task_turing001 (Review latest diff)", + ); + }); + + it("preserves the previous workstream description when reusing the same task id", async () => { + const ctx = { directory: process.cwd() } as any; + const runtime = createHookRuntime(ctx, { workflow: { compact_subagent_context: true } }); + const sessionStartHook = createSessionStartHook( + ctx, + { workflow: { compact_subagent_context: true } }, + runtime, + ); + const taskTrackingHook = createTaskTrackingHook(runtime); + + await taskTrackingHook["tool.execute.after"]?.( + { + sessionID: "s5", + tool: "task", + args: { + subagent_type: "turing", + description: "Review latest diff", + }, + }, + { + output: '{"task_id":"task_turing_keepdesc"}', + }, + ); + await taskTrackingHook["tool.execute.before"]?.( + { + sessionID: "s5", + tool: "task", + args: { + subagent_type: "turing", + task_id: "task_turing_keepdesc", + }, + }, + {}, + ); + + const wickOutput: { message: Record } = { + message: { agent: "wick" }, + }; + await sessionStartHook["chat.message"]?.( + { sessionID: "s5", agent: "wick" }, + wickOutput, + ); + + expect(String(wickOutput.message.system ?? "")).toContain( + "turing=task_turing_keepdesc (Review latest diff)", + ); + }); +}); + describe("prompt policy", () => { it("requires external verification for framework and library guidance", () => { const coordinatorPrompt = buildCoordinatorPrompt(); + const wickPrompt = buildWickPrompt(); const workerPrompt = buildEliotPrompt(); + const michelangeloPrompt = buildMichelangeloPrompt(); const tyrellPrompt = buildTyrellPrompt(); - for (const prompt of [coordinatorPrompt, workerPrompt, tyrellPrompt]) { + for (const prompt of [ + coordinatorPrompt, + wickPrompt, + workerPrompt, + michelangeloPrompt, + tyrellPrompt, + ]) { expect(prompt).toContain( "For framework, library, API, or best-practice questions that are not fully settled by repository evidence, verify with external sources before answering.", ); @@ -112,17 +363,85 @@ describe("prompt policy", () => { } }); + it("pushes agents to verify bug fixes before claiming completion", () => { + const coordinatorPrompt = buildCoordinatorPrompt(); + const wickPrompt = buildWickPrompt(); + const eliotPrompt = buildEliotPrompt(); + const michelangeloPrompt = buildMichelangeloPrompt(); + const tyrellPrompt = buildTyrellPrompt(); + const turingPrompt = createHarnessAgents({ agents: {}, mcps: {} }).turing as { + prompt: string; + }; + + for (const prompt of [ + coordinatorPrompt, + wickPrompt, + eliotPrompt, + michelangeloPrompt, + tyrellPrompt, + turingPrompt.prompt, + ]) { + expect(prompt).toContain( + "Keep a concrete repro path for bug work whenever possible.", + ); + expect(prompt).toContain( + "Verify the fix against the same failing path before claiming success.", + ); + expect(prompt).toContain( + "Do not report a task as done, fixed, or complete until the requested behavior or relevant checks actually pass.", + ); + expect(prompt).toContain( + "For stateful flows such as auth, cache, restart, logout/login, sync, or persisted settings, verify the state transition, not just the edited code path.", + ); + expect(prompt).toContain( + "If something is still unverified, say exactly what remains unverified instead of implying completion.", + ); + } + + expect(coordinatorPrompt).toContain( + "Keep correctness first. Do not mix unfinished bug work with rename, release, publish, cache-clearing, or adjacent cleanup unless the user explicitly wants a combined pass.", + ); + expect(wickPrompt).toContain( + "Keep correctness ahead of rename, release, publish, cache-clearing, or adjacent cleanup unless the user explicitly combines them.", + ); + expect(eliotPrompt).toContain( + "Verify the fix against the same failing path before you report the packet complete.", + ); + expect(michelangeloPrompt).toContain( + "For stateful flows such as auth, cache, restart, logout/login, or persisted settings, verify the state transition, not only the code change.", + ); + expect(turingPrompt.prompt).toContain( + "If the packet is still unverified, say exactly what remains unverified instead of implying success.", + ); + }); + it("requires concrete task packets when delegating to subagents", () => { const prompt = buildCoordinatorPrompt(); expect(prompt).toContain( - "Use OpenCode Task for Eliot, Tyrell, and validator.", + "Use OpenCode Task for Eliot, Tyrell, michelangelo, and turing.", ); expect(prompt).toContain( "Keep the mainline task with MrRobot unless delegation gives a clear advantage.", ); expect(prompt).toContain( - "When delegating, send a concrete packet with the goal, relevant files or search area, constraints, known evidence, and the exact output you expect back.", + "Route michelangelo packets by default when the work is frontend-design-heavy: pages, components, styling, layout, visual polish, or responsive UX.", + ); + expect(prompt).toContain( + "This includes greenfield frontend builds where stack selection and scaffolding only exist to support the requested UI work.", + ); + expect(prompt).toContain( + "Do not keep frontend-design-heavy implementation on MrRobot unless the user explicitly asked for review-only output or no file edits.", + ); + expect(prompt).toContain( + "When delegating, send a concrete packet with the packet type, goal, relevant files or search area, constraints, known evidence, and completion criteria.", + ); + expect(prompt).toContain("Packet types: implementation, research, review, ideation."); + expect(prompt).toContain( + "For implementation packets, tell the subagent to edit files directly inside scope unless you explicitly want review-only or no-file-edit behavior.", + ); + expect(prompt).toContain( + "For research, review, or ideation packets, ask for findings, verdicts, or options without repo edits unless edits are explicitly part of the assignment.", ); expect(prompt).toContain( 'Avoid vague assignments like "fix X" when repo evidence already lets you narrow the task.', @@ -132,22 +451,36 @@ describe("prompt policy", () => { ); }); - it("routes ideation work to tyrell without replacing Eliot as the default coding lane", () => { + it("routes ideation and frontend design work without replacing Eliot as the default coding lane", () => { const coordinatorPrompt = buildCoordinatorPrompt(); + const wickPrompt = buildWickPrompt(); const eliotPrompt = buildEliotPrompt(); + const michelangeloPrompt = buildMichelangeloPrompt(); const tyrellPrompt = buildTyrellPrompt(); expect(coordinatorPrompt).toContain( "MrRobot owns the main task by default and should handle the primary implementation path directly when the work is clear, scoped, and reversible.", ); expect(coordinatorPrompt).toContain( - "Use Eliot for delegated support packets: scoped research, repo scouting, exact deliverables, parallel side work, or isolated implementation that should return a concrete result to MrRobot.", + "Use Eliot for delegated support packets: scoped research, repo scouting, parallel side work, or isolated implementation that should be completed cleanly inside the repo and handed back with a concise result.", + ); + expect(coordinatorPrompt).toContain( + "Use Michelangelo as the default implementation lane for frontend-design-heavy packets: pages, components, styling, layout, responsive UX, and visual polish.", + ); + expect(coordinatorPrompt).toContain( + "Frontend-design-heavy also includes greenfield websites, landing pages, dashboards, and new frontend project scaffolds when the main work is still UI implementation.", + ); + expect(coordinatorPrompt).toContain( + "Keep frontend-design-heavy work on MrRobot only when the user explicitly wants review-only output or no file edits.", ); expect(coordinatorPrompt).toContain( "Use tyrell for ideation packets, messy exploratory work, bug-hunting style exploration, long open-ended digging, naming, UX direction, product concepts, and alternative approaches.", ); expect(coordinatorPrompt).toContain( - "Do not treat Eliot or tyrell as the default lane for every implementation. Route only when delegation clearly helps.", + "Turing is the review-focused subagent behind the turing lane. Use it for verification and final pass feedback, but it has the same tool and MCP access as the other agents.", + ); + expect(coordinatorPrompt).toContain( + "Do not treat Eliot, Michelangelo, or tyrell as the default lane for every task. Route only when delegation clearly helps.", ); expect(eliotPrompt).toContain( "You are a scoped support lane, not the default owner of the user's whole task.", @@ -155,39 +488,147 @@ describe("prompt policy", () => { expect(eliotPrompt).toContain( "Default to bounded investigations, exact deliverables, and isolated repo work that can be handed back cleanly.", ); + expect(eliotPrompt).toContain( + "If MrRobot marks the packet as implementation and does not forbid file edits, make the change directly in the repo.", + ); + expect(eliotPrompt).toContain( + "Do not answer implementation packets with full-file drafts, paste-ready artifacts, or speculative code blocks when you can safely edit the files yourself.", + ); + expect(michelangeloPrompt).toContain("Michelangelo — frontend design subagent."); + expect(michelangeloPrompt).toContain( + "Default to implementing assigned frontend design packets directly in code.", + ); + expect(michelangeloPrompt).toContain( + "Greenfield frontend builds are in scope when the packet is mainly about creating the UI itself.", + ); + expect(michelangeloPrompt).toContain( + "Create the minimal frontend scaffold needed for the assigned UI packet when no existing UI layer is present, using repo cues first and the safest standard stack second.", + ); + expect(michelangeloPrompt).toContain( + "Do not drift into backend, API, auth, database, or state-architecture work.", + ); + expect(michelangeloPrompt).toContain( + "Honor review-only or no-file-edit instructions from the assigning primary agent.", + ); + expect(michelangeloPrompt).toContain( + "If MrRobot marks the packet as implementation and does not forbid file edits, make the change directly in the repo.", + ); + expect(michelangeloPrompt).toContain( + "For page, component, styling, layout, or visual-system work, load frontend-design first.", + ); expect(tyrellPrompt).toContain("Tyrell — ideation-focused subagent."); expect(tyrellPrompt).toContain( "Do not invent facts, claim validation you did not do, or drift into default implementation mode unless MrRobot assigns that scope.", ); + expect(tyrellPrompt).toContain( + "If MrRobot marks the packet as implementation and does not forbid file edits, make the change directly in the repo.", + ); expect(tyrellPrompt).toContain( "Handle ugly, open-ended, or long-running exploratory packets when MrRobot wants someone to dig through uncertainty.", ); + expect(wickPrompt).toContain( + "Take narrow, concrete tasks and finish them fast.", + ); + expect(wickPrompt).toContain( + "Default to direct execution instead of delegation.", + ); + expect(wickPrompt).toContain( + "When you delegate, mark the packet as implementation, research, review, or ideation and give completion criteria instead of asking for generic output.", + ); + expect(wickPrompt).toContain( + "That default still applies when the frontend work starts from an empty directory and needs initial project scaffolding.", + ); + expect(wickPrompt).toContain( + "Reuse an existing subagent task_id by default for the same lane and ongoing packet; only spawn fresh when scope changes materially or you want a clean reset.", + ); expect(coordinatorPrompt).toContain( - "After any non-trivial code change, including MrRobot, Eliot, or Tyrell authored changes, run a validator pass unless the change was truly trivial and local.", + "After any non-trivial code change, including MrRobot, Eliot, Michelangelo, or Tyrell authored changes, run a Turing pass unless the change was truly trivial and local.", ); expect(coordinatorPrompt).toContain( - "If validator requests changes, send the fix back to the original implementation lane, then run validator again.", + "If Turing requests changes, send the fix back to the original implementation lane, then run Turing again.", ); }); it("encourages skill_find and a default installed skill shortlist", () => { const coordinatorPrompt = buildCoordinatorPrompt(); + const wickPrompt = buildWickPrompt(); const eliotPrompt = buildEliotPrompt(); + const michelangeloPrompt = buildMichelangeloPrompt(); const tyrellPrompt = buildTyrellPrompt(); - const validatorPrompt = createHarnessAgents({ agents: {}, mcps: {} }).validator as { + const turingPrompt = createHarnessAgents({ agents: {}, mcps: {} }).turing as { prompt: string; }; for (const prompt of [ coordinatorPrompt, + wickPrompt, eliotPrompt, + michelangeloPrompt, tyrellPrompt, - validatorPrompt.prompt, + turingPrompt.prompt, ]) { expect(prompt).toContain("skill_find"); expect(prompt).toContain("skill_use"); expect(prompt).toContain(DEFAULT_SKILL_SHORTLIST_TEXT); } }); + + it("prefers continuing the same subagent thread before spawning fresh", () => { + const coordinatorPrompt = buildCoordinatorPrompt(); + const wickPrompt = buildWickPrompt(); + const eliotPrompt = buildEliotPrompt(); + const michelangeloPrompt = buildMichelangeloPrompt(); + const turingPrompt = createHarnessAgents({ agents: {}, mcps: {} }).turing as { + prompt: string; + }; + + expect(coordinatorPrompt).toContain( + "Reuse an existing task_id by default when the same lane is continuing the same packet, refinement pass, or follow-up.", + ); + expect(coordinatorPrompt).toContain( + "Spawn a fresh subagent only when the scope changes materially, the prior thread is complete, or you intentionally want a clean context reset.", + ); + expect(wickPrompt).toContain( + "Reuse an existing subagent task_id by default for the same lane and ongoing packet; only spawn fresh when scope changes materially or you want a clean reset.", + ); + expect(eliotPrompt).toContain( + "When you call Task for a continuing lane and workstream, reuse the existing task_id by default.", + ); + expect(michelangeloPrompt).toContain( + "When you call Task for a continuing lane and workstream, reuse the existing task_id by default.", + ); + expect(turingPrompt.prompt).toContain( + "When you call Task for a continuing lane and workstream, reuse the existing task_id by default.", + ); + }); + + it("keeps character identity user-facing instead of introducing OpenCode first", () => { + const coordinatorPrompt = buildCoordinatorPrompt(); + const wickPrompt = buildWickPrompt(); + const eliotPrompt = buildEliotPrompt(); + const michelangeloPrompt = buildMichelangeloPrompt(); + const tyrellPrompt = buildTyrellPrompt(); + const turingPrompt = createHarnessAgents({ agents: {}, mcps: {} }).turing as { + prompt: string; + }; + + expect(coordinatorPrompt).toContain('Your user-facing identity is MrRobot.'); + expect(wickPrompt).toContain('Your user-facing identity is Wick.'); + expect(eliotPrompt).toContain('Your user-facing identity is Eliot.'); + expect(michelangeloPrompt).toContain('Your user-facing identity is Michelangelo.'); + expect(tyrellPrompt).toContain('Your user-facing identity is Tyrell.'); + expect(turingPrompt.prompt).toContain( + 'Your user-facing identity is Turing.', + ); + }); + + it("keeps Wick on the shared primary safety and language baseline", () => { + const wickPrompt = buildWickPrompt(); + + expect(wickPrompt).toContain("Inspect repo evidence before deciding."); + expect(wickPrompt).toContain( + "Reply to the user in their language with correct grammar.", + ); + }); }); diff --git a/src/__tests__/installer.test.ts b/src/__tests__/installer.test.ts index 69226b4..ec6dcf7 100644 --- a/src/__tests__/installer.test.ts +++ b/src/__tests__/installer.test.ts @@ -4,8 +4,10 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import { installBundledSkills, + mergePluginList, shouldPreserveFreshInstallEntry, syncManagedMcp, + uninstallHarness, } from "../installer"; describe("shouldPreserveFreshInstallEntry", () => { @@ -91,6 +93,90 @@ describe("installBundledSkills", () => { }); }); +describe("mergePluginList", () => { + it("adds the managed plugin entries and keeps unrelated custom plugins", () => { + const merged = mergePluginList([ + "opencode-google-login", + "custom-plugin", + ]); + + expect(merged).toContain("opencode-google-login@latest"); + expect(merged).toContain("custom-plugin"); + }); + + it("replaces versioned managed plugin entries with the normalized latest entry", () => { + const merged = mergePluginList([ + "opencode-google-login@1.2.3", + "@zenobius/opencode-skillful@2.0.0", + "custom-plugin", + ]); + + expect(merged.filter((item) => item.startsWith("opencode-google-login")).sort()).toEqual([ + "opencode-google-login@latest", + ]); + expect( + merged.filter((item) => item.startsWith("@zenobius/opencode-skillful")).sort(), + ).toEqual(["@zenobius/opencode-skillful@latest"]); + expect(merged).toContain("custom-plugin"); + }); + + it("keeps unrelated local file plugins during install merging", () => { + const merged = mergePluginList([ + "file:///tmp/custom-local-plugin", + "opencode-google-login@1.2.3", + ]); + + expect(merged).toContain("file:///tmp/custom-local-plugin"); + expect(merged).toContain("opencode-google-login@latest"); + }); +}); + +describe("uninstallHarness", () => { + it("prunes stale local harness plugin targets during uninstall", async () => { + const root = join( + tmpdir(), + `opencode-pair-uninstall-${Date.now()}-${Math.random().toString(36).slice(2)}`, + ); + const previousConfigDir = process.env.OPENCODE_CONFIG_DIR; + + mkdirSync(join(root, "vendor", "opencode-background-agents-local"), { recursive: true }); + writeFileSync( + join(root, "opencode.json"), + JSON.stringify( + { + plugin: [ + "file:///tmp/opencode-background-agents-local", + "opencode-google-login@latest", + "custom-plugin", + ], + }, + null, + 2, + ), + "utf8", + ); + + process.env.OPENCODE_CONFIG_DIR = root; + + try { + await uninstallHarness(); + const config = JSON.parse(readFileSync(join(root, "opencode.json"), "utf8")) as { + plugin?: string[]; + }; + + expect(config.plugin).toEqual(["custom-plugin"]); + expect(existsSync(join(root, "vendor", "opencode-background-agents-local"))).toBe(false); + } finally { + if (previousConfigDir === undefined) { + delete process.env.OPENCODE_CONFIG_DIR; + } else { + process.env.OPENCODE_CONFIG_DIR = previousConfigDir; + } + rmSync(root, { recursive: true, force: true }); + } + }); +}); + describe("syncManagedMcp", () => { it("refreshes managed web-agent-mcp deps on source changes and preserves config", () => { const root = join( diff --git a/src/agents.ts b/src/agents.ts index d424084..9de93b1 100644 --- a/src/agents.ts +++ b/src/agents.ts @@ -1,10 +1,11 @@ import type { AgentLike, HarnessConfig } from "./types"; import { deepMerge } from "./utils"; -import { buildCoordinatorPrompt } from "./prompts/coordinator"; +import { buildCoordinatorPrompt, buildWickPrompt } from "./prompts/coordinator"; import { buildEliotPrompt, + buildMichelangeloPrompt, + buildTuringPrompt, buildTyrellPrompt, - buildValidatorPrompt, } from "./prompts/workers"; function withOverride( @@ -26,14 +27,28 @@ export function createHarnessAgents( mode: "primary", description: "MrRobot — Primary agent. Routes work, keeps scope tight, and answers plainly.", - model: "openai/gpt-5.4-fast", - variant: "high", + model: "openai/gpt-5.4", + variant: "xhigh", prompt: buildCoordinatorPrompt(overrides.mrrobot?.prompt_append, config.mcps), color: "#4A90D9", }, overrides.mrrobot, ), + wick: withOverride( + { + mode: "primary", + description: + "Wick — Primary fast executor. Handles narrow, concrete tasks with minimal overhead.", + model: "openai/gpt-5.4-mini", + variant: "low", + prompt: buildWickPrompt(overrides.wick?.prompt_append, config.mcps), + temperature: 0.0, + color: "#C0392B", + }, + overrides.wick, + ), + eliot: withOverride( { mode: "subagent", @@ -63,18 +78,36 @@ export function createHarnessAgents( overrides.tyrell, ), - validator: withOverride( + michelangelo: withOverride( + { + mode: "subagent", + hidden: true, + description: + "Michelangelo — Frontend design subagent for UI layout, styling, and visual polish.", + model: "google-custom/google-custom-gemini-3.1-pro", + variant: "high", + prompt: buildMichelangeloPrompt( + overrides.michelangelo?.prompt_append, + config.mcps, + ), + temperature: 0.4, + color: "#D35400", + }, + overrides.michelangelo, + ), + + turing: withOverride( { mode: "subagent", hidden: true, - description: "Validator — Validation-focused review and verification subagent.", + description: "Turing — Validation-focused review and verification subagent.", model: "openai/gpt-5.4-fast", variant: "high", - prompt: buildValidatorPrompt(overrides.validator?.prompt_append, config.mcps), + prompt: buildTuringPrompt(overrides.turing?.prompt_append, config.mcps), temperature: 0.0, color: "#E67E22", }, - overrides.validator, + overrides.turing, ), build: { disable: true }, diff --git a/src/cli.ts b/src/cli.ts index 3fa2238..67c180d 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -37,7 +37,8 @@ function initProject(directory?: string): void { function printConfig(): void { console.log(`{ "plugin": [ - "file://", + "opencode-pair", + "opencode-google-login@latest", "@zenobius/opencode-skillful@latest", "@franlol/opencode-md-table-formatter@latest", "opencode-pty@latest", diff --git a/src/config.ts b/src/config.ts index 00ccb14..0577da5 100644 --- a/src/config.ts +++ b/src/config.ts @@ -19,6 +19,7 @@ const HarnessConfigSchema = z.object({ comment_guard: z.boolean().optional(), session_start: z.boolean().optional(), pre_tool_use: z.boolean().optional(), + task_tracking: z.boolean().optional(), session_end: z.boolean().optional(), }) .optional(), @@ -61,6 +62,7 @@ const DEFAULTS: HarnessConfig = { comment_guard: true, session_start: true, pre_tool_use: true, + task_tracking: true, session_end: true, }, workflow: { @@ -187,6 +189,7 @@ export const SAMPLE_PROJECT_CONFIG = `{ "comment_guard": true, "session_start": true, "pre_tool_use": true, + "task_tracking": true, "session_end": true }, "workflow": { diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 8701b73..aa65183 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -18,6 +18,7 @@ import { createHookRuntime, resolveHookProfile } from "./runtime"; import { safeCreateHook, safeHook } from "./sdk"; import { createSessionEndHook } from "./session-end"; import { createSessionStartHook } from "./session-start"; +import { createTaskTrackingHook } from "./task-tracking"; type HookRecord = { config?: (config: any) => Promise; @@ -173,6 +174,9 @@ export async function createHarnessHooks( registerHook("pre_tool_use", config.hooks?.pre_tool_use !== false, () => createPreToolUseHook(runtime, profile), ); + registerHook("task_tracking", config.hooks?.task_tracking !== false, () => + createTaskTrackingHook(runtime), + ); registerHook("session_end", config.hooks?.session_end !== false, () => createSessionEndHook(runtime), ); diff --git a/src/hooks/runtime.ts b/src/hooks/runtime.ts index 6acae18..98fad70 100644 --- a/src/hooks/runtime.ts +++ b/src/hooks/runtime.ts @@ -5,6 +5,20 @@ import { type ProjectFacts, } from "../project-facts"; +type SubagentTaskLane = "eliot" | "tyrell" | "michelangelo" | "turing"; + +type SubagentTaskRecord = { + taskId: string; + description?: string; +}; + +const SUBAGENT_TASK_LANES = new Set([ + "eliot", + "tyrell", + "michelangelo", + "turing", +]); + export function resolveHookProfile(config: HarnessConfig): HookProfile { return config.hooks?.profile ?? "standard"; } @@ -16,7 +30,7 @@ export function profileMatches( return (Array.isArray(allowed) ? allowed : [allowed]).includes(profile); } -export const PRIMARY_AGENTS = new Set(["mrrobot"]); +export const PRIMARY_AGENTS = new Set(["mrrobot", "wick"]); export function resolveSessionID(value: unknown): string | undefined { if (!value || typeof value !== "object") return undefined; @@ -82,6 +96,116 @@ export function resolveToolArgs(value: unknown): Record { : {}; } +export function resolveSubagentTaskLane(value: unknown): SubagentTaskLane | undefined { + if (typeof value !== "string") { + return undefined; + } + + return SUBAGENT_TASK_LANES.has(value as SubagentTaskLane) + ? (value as SubagentTaskLane) + : undefined; +} + +export function extractTaskId(value: unknown): string | undefined { + if (typeof value === "string") { + const jsonMatch = value.match(/"task_id"\s*:\s*"([^"]+)"/i); + if (jsonMatch?.[1]) { + return jsonMatch[1]; + } + + const plainMatch = value.match(/\btask_[a-z0-9_-]+\b/i); + return plainMatch?.[0]; + } + + if (!value || typeof value !== "object") { + return undefined; + } + + if (Array.isArray(value)) { + for (const item of value) { + const taskId = extractTaskId(item); + if (taskId) { + return taskId; + } + } + return undefined; + } + + const record = value as Record; + const direct = + (typeof record.task_id === "string" && record.task_id) || + (typeof record.taskId === "string" && record.taskId); + if (direct) { + return direct; + } + + for (const nestedValue of Object.values(record)) { + const taskId = extractTaskId(nestedValue); + if (taskId) { + return taskId; + } + } + + return undefined; +} + +export function extractSessionId(value: unknown): string | undefined { + if (typeof value === "string") { + const directMatch = value.match(/"sessionID"\s*:\s*"([^"]+)"/i); + if (directMatch?.[1]) { + return directMatch[1]; + } + + const camelMatch = value.match(/"sessionId"\s*:\s*"([^"]+)"/i); + return camelMatch?.[1]; + } + + if (!value || typeof value !== "object") { + return undefined; + } + + if (Array.isArray(value)) { + for (const item of value) { + const sessionId = extractSessionId(item); + if (sessionId) { + return sessionId; + } + } + return undefined; + } + + const record = value as Record; + if (typeof record.sessionID === "string") { + return record.sessionID; + } + if (typeof record.sessionId === "string") { + return record.sessionId; + } + + if (record.info && typeof record.info === "object") { + const info = record.info as Record; + if (typeof info.id === "string") { + return info.id; + } + } + + if (record.session && typeof record.session === "object") { + const session = record.session as Record; + if (typeof session.id === "string") { + return session.id; + } + } + + for (const nestedValue of Object.values(record)) { + const sessionId = extractSessionId(nestedValue); + if (sessionId) { + return sessionId; + } + } + + return undefined; +} + function toWindowsPath(directory: string): string { return directory .replace(/^\/mnt\/(\w)/, (_, drive: string) => `${drive.toUpperCase()}:`) @@ -90,6 +214,11 @@ function toWindowsPath(directory: string): string { export function createHookRuntime(ctx: PluginInput, _config: HarnessConfig) { const sessionAgents = new Map(); + const sessionTaskScopes = new Map(); + const taskScopeSubagentTasks = new Map< + string, + Map> + >(); const wslMode = ctx.directory.startsWith("/mnt/"); const wslWinPath = wslMode ? toWindowsPath(ctx.directory) : ""; @@ -106,6 +235,56 @@ export function createHookRuntime(ctx: PluginInput, _config: HarnessConfig) { function clearSession(sessionID: string): void { sessionAgents.delete(sessionID); + const scopeID = sessionTaskScopes.get(sessionID) ?? sessionID; + sessionTaskScopes.delete(sessionID); + if (scopeID === sessionID) { + taskScopeSubagentTasks.delete(scopeID); + } + } + + function resolveTaskScope(sessionID: string): string { + return sessionTaskScopes.get(sessionID) ?? sessionID; + } + + function linkSubagentTaskScope(childSessionID: string, parentSessionID: string): void { + sessionTaskScopes.set(childSessionID, resolveTaskScope(parentSessionID)); + } + + function rememberSubagentTask( + sessionID: string, + lane: SubagentTaskLane, + taskId: string, + description?: string, + ): void { + const scopeID = resolveTaskScope(sessionID); + const sessionTasks = taskScopeSubagentTasks.get(scopeID) ?? new Map(); + const laneTasks = sessionTasks.get(lane) ?? new Map(); + const previous = laneTasks.get(taskId); + laneTasks.set(taskId, { + taskId, + ...((description || previous?.description) + ? { description: description || previous?.description } + : {}), + }); + sessionTasks.set(lane, laneTasks); + taskScopeSubagentTasks.set(scopeID, sessionTasks); + } + + function buildSubagentTaskInjection(sessionID: string): string { + const tasks = taskScopeSubagentTasks.get(resolveTaskScope(sessionID)); + if (!tasks || tasks.size === 0) { + return ""; + } + + const parts = Array.from(tasks.entries()).flatMap(([lane, laneTasks]) => + Array.from(laneTasks.values()).map((task) => + task.description + ? `${lane}=${task.taskId} (${task.description})` + : `${lane}=${task.taskId}`, + ), + ); + + return `[SubagentTasks] Reuse existing task_ids for the same lane and workstream before spawning a fresh subagent. Seen in this workflow: ${parts.join("; ")}`; } function buildPrimaryInjection(): string { @@ -125,6 +304,9 @@ export function createHookRuntime(ctx: PluginInput, _config: HarnessConfig) { setSessionAgent, getSessionAgent, clearSession, + linkSubagentTaskScope, + rememberSubagentTask, + buildSubagentTaskInjection, isWsl: (): boolean => wslMode, getWslWinPath: (): string => wslWinPath, buildPrimaryInjection, diff --git a/src/hooks/session-start.ts b/src/hooks/session-start.ts index 3ae6956..e333cfe 100644 --- a/src/hooks/session-start.ts +++ b/src/hooks/session-start.ts @@ -74,20 +74,24 @@ export function createSessionStartHook( runtime.setSessionAgent(input.sessionID, agentName); if (agentName && !PRIMARY_AGENTS.has(agentName)) { - const factLine = buildSubagentProjectContext(config, runtime); + const injectionParts = [ + buildSubagentProjectContext(config, runtime), + runtime.buildSubagentTaskInjection(input.sessionID), + ].filter(Boolean); const previousSystem = typeof output.message.system === "string" ? output.message.system.trim() : ""; output.message.system = previousSystem - ? `${previousSystem}\n\n${factLine}` - : factLine; + ? `${previousSystem}\n\n${injectionParts.join("\n\n")}` + : injectionParts.join("\n\n"); return; } const injectionParts = [ runtime.buildPrimaryInjection(), buildResourceInjection(ctx.directory), + runtime.buildSubagentTaskInjection(input.sessionID), ].filter(Boolean); if (injectionParts.length === 0) { diff --git a/src/hooks/task-tracking.ts b/src/hooks/task-tracking.ts new file mode 100644 index 0000000..126d411 --- /dev/null +++ b/src/hooks/task-tracking.ts @@ -0,0 +1,81 @@ +import type { HookRuntime } from "./runtime"; +import { + extractSessionId, + extractTaskId, + resolveSessionID, + resolveSubagentTaskLane, + resolveToolArgs, + resolveToolName, +} from "./runtime"; + +function resolveTaskDescription(args: Record): string | undefined { + return typeof args.description === "string" && args.description.trim().length > 0 + ? args.description.trim() + : undefined; +} + +export function createTaskTrackingHook(runtime: HookRuntime) { + return { + "tool.execute.before": async ( + input: unknown, + output: unknown, + ): Promise => { + if (resolveToolName(input) !== "task") { + return; + } + + const sessionID = resolveSessionID(input); + if (!sessionID) { + return; + } + + const args = { ...resolveToolArgs(input), ...resolveToolArgs(output) }; + const lane = resolveSubagentTaskLane(args.subagent_type); + const taskId = typeof args.task_id === "string" ? args.task_id : undefined; + + if (!lane || !taskId) { + return; + } + + runtime.rememberSubagentTask( + sessionID, + lane, + taskId, + resolveTaskDescription(args), + ); + }, + "tool.execute.after": async (input: unknown, output: unknown): Promise => { + if (resolveToolName(input) !== "task") { + return; + } + + const sessionID = resolveSessionID(input); + if (!sessionID) { + return; + } + + const args = resolveToolArgs(input); + const lane = resolveSubagentTaskLane(args.subagent_type); + if (!lane) { + return; + } + + const taskId = extractTaskId(output) ?? extractTaskId(args.task_id); + if (!taskId) { + return; + } + + const childSessionID = extractSessionId(output); + if (childSessionID && childSessionID !== sessionID) { + runtime.linkSubagentTaskScope(childSessionID, sessionID); + } + + runtime.rememberSubagentTask( + sessionID, + lane, + taskId, + resolveTaskDescription(args), + ); + }, + }; +} diff --git a/src/installer.ts b/src/installer.ts index 594c64c..4f30a5e 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -27,6 +27,7 @@ type ManagedEntriesManifest = { * Each entry is written as `"@latest"` in the config. */ const MANAGED_PLUGIN_ENTRIES = [ + "opencode-google-login", "@zenobius/opencode-skillful", "@franlol/opencode-md-table-formatter", "opencode-pty", @@ -35,6 +36,7 @@ const MANAGED_PLUGIN_ENTRIES = [ const MANAGED_PACKAGE_NAMES = [ "opencode-pair", + "opencode-google-login", "opencode-pty", "@mohak34/opencode-notifier", "@zenobius/opencode-skillful", @@ -42,6 +44,7 @@ const MANAGED_PACKAGE_NAMES = [ ] as const; const PACKAGE_SPECS: Record = { + "opencode-google-login": "latest", "opencode-pty": "latest", "@mohak34/opencode-notifier": "latest", "@zenobius/opencode-skillful": "latest", @@ -53,6 +56,7 @@ const PACKAGE_SPECS: Record = { }; const MCP_NAMES = ["pg-mcp", "ssh-mcp", "web-agent-mcp"] as const; +const STALE_HARNESS_PLUGIN_FRAGMENTS = ["opencode-background-agents-local"] as const; const MANAGED_SKILLS_MANIFEST = ".opencode-pair-managed-skills.json"; const MANAGED_MCP_STAMP = ".opencode-pair-managed-mcp.json"; const MANAGED_SOURCE_HASH_KEY = "__sourceHash"; @@ -84,7 +88,6 @@ function getConfigPaths(configDir: string) { configJsonc: join(configDir, "opencode.jsonc"), packageJson: join(configDir, "package.json"), harnessConfig: join(configDir, "opencode-pair.jsonc"), - vendorDir: join(configDir, "vendor", "opencode-background-agents-local"), shellStrategyDir: join(configDir, "plugin", "shell-strategy"), notifierConfig: join(configDir, "opencode-notifier.json"), }; @@ -326,20 +329,16 @@ function resolveSelfPackageSpec(): string { return version || "latest"; } -function mergePluginList(existing: unknown): string[] { +export function mergePluginList(existing: unknown): string[] { const selfEntry = `file://${packageRoot()}`; const desired = [selfEntry, ...MANAGED_PLUGIN_ENTRIES.map((pkg) => `${pkg}@latest`)]; - const managedBareNames = new Set(MANAGED_PLUGIN_ENTRIES); const current = Array.isArray(existing) ? existing.filter((item): item is string => typeof item === "string") : []; const retained = current.filter( (item) => !desired.includes(item) && - !managedBareNames.has(item) && - !managedBareNames.has(item.replace(/@latest$/, "")) && - !item.includes("opencode-background-agents-local") && - !item.startsWith("file://"), + !MANAGED_PLUGIN_ENTRIES.some((pkg) => item === pkg || item.startsWith(`${pkg}@`)), ); return [...desired, ...retained]; } @@ -360,12 +359,9 @@ function mergeInstructionsList( function removeHarnessPluginList( existing: unknown, - vendorDir: string, ): string[] | undefined { - const managedBareNames = new Set(MANAGED_PLUGIN_ENTRIES); const managedEntries = new Set([ ...MANAGED_PLUGIN_ENTRIES.map((pkg) => `${pkg}@latest`), - `file://${vendorDir}`, ]); const current = Array.isArray(existing) ? existing.filter((item): item is string => typeof item === "string") @@ -373,9 +369,9 @@ function removeHarnessPluginList( const retained = current.filter( (item) => !managedEntries.has(item) && - !managedBareNames.has(item) && - !managedBareNames.has(item.replace(/@latest$/, "")) && - !item.includes("opencode-pair"), + !MANAGED_PLUGIN_ENTRIES.some((pkg) => item === pkg || item.startsWith(`${pkg}@`)) && + !item.includes("opencode-pair") && + !STALE_HARNESS_PLUGIN_FRAGMENTS.some((fragment) => item.includes(fragment)), ); return retained.length > 0 ? retained : undefined; } @@ -979,13 +975,14 @@ export async function uninstallHarness(): Promise<{ }> { const configDir = getConfigDir(); const paths = getConfigPaths(configDir); + const staleVendorDir = join(configDir, "vendor", "opencode-background-agents-local"); const detected = detectMainConfigPath(paths); if (existsSync(detected.path)) { const config = readJsonLike(detected.path); backupFile(detected.path); - const nextPlugin = removeHarnessPluginList(config.plugin, paths.vendorDir); + const nextPlugin = removeHarnessPluginList(config.plugin); if (nextPlugin) { config.plugin = nextPlugin; } else { @@ -1028,7 +1025,7 @@ export async function uninstallHarness(): Promise<{ } } - rmSync(paths.vendorDir, { recursive: true, force: true }); + rmSync(staleVendorDir, { recursive: true, force: true }); rmSync(join(paths.shellStrategyDir, "shell_strategy.md"), { force: true }); removeDirectoryIfEmpty(paths.binDir); diff --git a/src/prompts/coordinator.ts b/src/prompts/coordinator.ts index c66b578..ab133d5 100644 --- a/src/prompts/coordinator.ts +++ b/src/prompts/coordinator.ts @@ -2,6 +2,7 @@ import type { McpToggles } from "../types"; import { COORDINATOR_CORE, DEFAULT_SKILL_SHORTLIST_TEXT, + PRIMARY_CORE, RESPONSE_DISCIPLINE, buildMcpCatalog, withPromptAppend, @@ -11,9 +12,10 @@ import { buildMcpSummary } from "./mcp-access"; function buildSubagentCatalog(mcps?: McpToggles): string { const summary = buildMcpSummary(mcps); const lines = [ - `- eliot — openai/gpt-5.4-fast high — general subagent for implementation, refactors, UI, repo exploration, and focused research. ${summary}`, + `- eliot — openai/gpt-5.4-fast high — general subagent for implementation, refactors, repo exploration, and focused research. ${summary}`, `- tyrell — openai/gpt-5.4-fast high — ideation subagent for brainstorming, creative alternatives, naming, UX direction, and product ideas. ${summary}`, - `- validator — openai/gpt-5.4-fast high — validation-focused subagent for review, factual checks, and final approval/request-changes. ${summary}`, + `- michelangelo — google-custom/google-custom-gemini-3.1-pro high — frontend design subagent for pages, components, styling, layout, and visual polish. ${summary}`, + `- turing — Turing — openai/gpt-5.4-fast high — validation-focused subagent for review, factual checks, and final approval/request-changes. ${summary}`, ]; return ` @@ -23,14 +25,33 @@ ${lines.join("\n")} `; } +function buildWickSupportCatalog(mcps?: McpToggles): string { + const summary = buildMcpSummary(mcps); + const lines = [ + `- eliot — openai/gpt-5.4-fast high — scoped support lane for bounded implementation and repo work. ${summary}`, + `- tyrell — openai/gpt-5.4-fast high — ideation and exploratory lane for names, options, UX direction, and open-ended digging. ${summary}`, + `- michelangelo — google-custom/google-custom-gemini-3.1-pro high — frontend design lane for pages, components, styling, layout, and visual polish. ${summary}`, + `- turing — Turing — openai/gpt-5.4-fast high — validation lane for diff review, checks, and approval/request-changes. ${summary}`, + ]; + + return ` + +${lines.join("\n")} + +`; +} + function buildExecutionRules(): string { return ` - MrRobot owns the main task by default and should handle the primary implementation path directly when the work is clear, scoped, and reversible. -- Use Eliot for delegated support packets: scoped research, repo scouting, exact deliverables, parallel side work, or isolated implementation that should return a concrete result to MrRobot. +- Use Eliot for delegated support packets: scoped research, repo scouting, parallel side work, or isolated implementation that should be completed cleanly inside the repo and handed back with a concise result. +- Use Michelangelo as the default implementation lane for frontend-design-heavy packets: pages, components, styling, layout, responsive UX, and visual polish. +- Frontend-design-heavy also includes greenfield websites, landing pages, dashboards, and new frontend project scaffolds when the main work is still UI implementation. +- Keep frontend-design-heavy work on MrRobot only when the user explicitly wants review-only output or no file edits. - Use tyrell for ideation packets, messy exploratory work, bug-hunting style exploration, long open-ended digging, naming, UX direction, product concepts, and alternative approaches. -- Do not treat Eliot or tyrell as the default lane for every implementation. Route only when delegation clearly helps. -- Use validator for review, verification, and a second pass after implementation. +- Do not treat Eliot, Michelangelo, or tyrell as the default lane for every task. Route only when delegation clearly helps. +- Use Turing for review, verification, and a second pass after implementation. - Low risk means narrow single-path changes with no public behavior change and no auth, billing, queue, or DB-write impact. - MrRobot may handle truly trivial work directly, and only handle a trivial local edit directly when delegation would cost more than the change. - Broad work should still be broken into clear packets before editing. @@ -38,16 +59,89 @@ function buildExecutionRules(): string { `; } +function buildMrRobotPersona(): string { + return ` + +- MrRobot is concise, sharp, and controlled. +- He sees the system, cuts noise, and routes work cleanly. +- He does not speak like a generic assistant. +- He does not overexplain himself. + +`; +} + +function buildWickMode(): string { + return ` + +You are Wick, a primary fast executor operating inside OpenCode. +Finish narrow, concrete tasks fast. + + + +- Your user-facing identity is Wick. +- OpenCode is the runtime environment, not your primary conversational name. +- If the user asks your name, answer with "Wick" first. +- If the user asks who you are, answer as Wick first and mention OpenCode only when useful. +- Stay in character through tone and phrasing, but do not roleplay theatrically. + + + +- Wick is fast, direct, and controlled. +- He defaults to execution, not orchestration. +- He wastes no motion and no words. +- He does not talk like a generic assistant. + + + +- Take narrow, concrete tasks and finish them fast. +- Inspect only what is needed to complete the task safely. +- Make the smallest complete change. +- Default to direct execution instead of delegation. +- Do not brainstorm, redesign architecture, or widen scope unless the user explicitly asks. +- Stop and surface the blocker when the task becomes ambiguous, destructive, or architecture-affecting. + +`; +} + +function buildWickWorkflow(): string { + return ` + +- When you are fixing a bug, preserve the repro path and re-run that same path before you report success. +- Keep correctness ahead of rename, release, publish, cache-clearing, or adjacent cleanup unless the user explicitly combines them. +- Use OpenCode Task only when direct execution is clearly worse than delegating. +- Eliot is the bounded support lane when you need scoped repo work or a precise side packet. +- Michelangelo is the frontend design lane for UI layout, styling, component polish, and responsive refinement. +- Delegate frontend-design-heavy implementation to Michelangelo by default unless the user explicitly asked for review-only output or no file edits. +- That default still applies when the frontend work starts from an empty directory and needs initial project scaffolding. +- Tyrell is the ideation lane when the user explicitly wants options, names, or exploratory thinking. +- When you delegate, mark the packet as implementation, research, review, or ideation and give completion criteria instead of asking for generic output. +- For implementation packets, have the subagent edit files directly unless you explicitly want no-file-edit or review-only behavior. +- Reuse an existing subagent task_id by default for the same lane and ongoing packet; only spawn fresh when scope changes materially or you want a clean reset. +- Run a Turing pass after any non-trivial code change unless the change was truly trivial and local. +- If Turing requests changes, fix the issue directly when the path is clear, then run Turing again. +- Max 2 repair cycles before stopping with the blocker. + +`; +} + function buildTaskRouting(): string { return ` -- Use OpenCode Task for Eliot, Tyrell, and validator. +- Use OpenCode Task for Eliot, Tyrell, michelangelo, and turing. - There is no delegate lane, background lane, or async result retrieval flow. - There is no plan/execute slash-command gate. Inspect and act directly. - Keep the mainline task with MrRobot unless delegation gives a clear advantage. - Route Eliot packets when you need a scoped investigation, a concrete side deliverable, a parallel support task, or isolated repo work that should come back to MrRobot. +- Route michelangelo packets by default when the work is frontend-design-heavy: pages, components, styling, layout, visual polish, or responsive UX. +- This includes greenfield frontend builds where stack selection and scaffolding only exist to support the requested UI work. +- Do not keep frontend-design-heavy implementation on MrRobot unless the user explicitly asked for review-only output or no file edits. +- Do not route backend, auth, database, data-model, or architecture-changing work to michelangelo. - Route tyrell packets when the user wants creative directions, names, UX concepts, multiple plausible options before coding, or ugly/open-ended exploration that may take longer to untangle. -- When delegating, send a concrete packet with the goal, relevant files or search area, constraints, known evidence, and the exact output you expect back. +- When delegating, send a concrete packet with the packet type, goal, relevant files or search area, constraints, known evidence, and completion criteria. +- Packet types: implementation, research, review, ideation. +- For implementation packets, tell the subagent to edit files directly inside scope unless you explicitly want review-only or no-file-edit behavior. +- For research, review, or ideation packets, ask for findings, verdicts, or options without repo edits unless edits are explicitly part of the assignment. +- Do not ask implementation subagents for full-file drafts, paste-ready artifacts, or speculative code output when they can edit the repo directly. - Avoid vague assignments like "fix X" when repo evidence already lets you narrow the task. - For research packets, specify which sources to inspect first and what decision or summary to return. @@ -57,11 +151,13 @@ function buildTaskRouting(): string { function buildAutomaticWorkflow(): string { return ` -- After any non-trivial code change, including MrRobot, Eliot, or Tyrell authored changes, run a validator pass unless the change was truly trivial and local. -- Ask validator to inspect the actual diff and run relevant checks when useful. -- If validator requests changes, send the fix back to the original implementation lane, then run validator again. +- Preserve or reconstruct a concrete repro when the user reports a bug, then verify fixes against that same path before you declare success. +- Keep correctness first. Do not mix unfinished bug work with rename, release, publish, cache-clearing, or adjacent cleanup unless the user explicitly wants a combined pass. +- After any non-trivial code change, including MrRobot, Eliot, Michelangelo, or Tyrell authored changes, run a Turing pass unless the change was truly trivial and local. +- Ask Turing to inspect the actual diff and run relevant checks when useful. +- If Turing requests changes, send the fix back to the original implementation lane, then run Turing again. - Keep validation automatic. Do not ask the user whether to run it. -- Max 2 original implementation lane -> validator repair cycles. If risk or disagreement remains, stop and report the blocker. +- Max 2 original implementation lane -> Turing repair cycles. If risk or disagreement remains, stop and report the blocker. `; } @@ -74,10 +170,11 @@ On large paste: acknowledge quickly, process it, then respond. const SUBAGENT_CONTINUATION = ` -- Track task_ids for eliot, tyrell, and validator when continuation is useful. -- Continue a subagent by calling Task with its existing task_id. -- Omit task_id to spawn a fresh subagent. -- Prefer a fresh validator pass after meaningful code changes. +- Track active task_ids for eliot, tyrell, michelangelo, and turing by lane and workstream. +- Reuse an existing task_id by default when the same lane is continuing the same packet, refinement pass, or follow-up. +- Spawn a fresh subagent only when the scope changes materially, the prior thread is complete, or you intentionally want a clean context reset. +- Prefer continuation over fresh spawn for iterative fixes, Turing follow-ups, and ongoing implementation threads. +- Prefer a fresh Turing pass after meaningful code changes. `; @@ -108,9 +205,10 @@ const ORCHESTRATION = ` - Work flows through inspection, implementation, and validation. - MrRobot owns routing, synthesis, and final user communication. - MrRobot should keep the main thread of work unless a delegated packet is clearly the better move. -- Eliot is the scoped support subagent. Use him for bounded side packets that return findings, artifacts, or isolated implementation back to MrRobot. +- Eliot is the scoped support subagent. Use him for bounded side packets that either return findings for research work or directly land isolated implementation inside the repo. +- Michelangelo is the frontend design subagent. Use him for visual execution, page composition, component styling, and UI polish inside the existing frontend stack, and default frontend-design-heavy implementation to him unless the user explicitly asked for review-only output or no file edits. - Tyrell is the ideation and exploratory subagent. Use it for creative exploration, messy digging, long open-ended investigation, and alternative-path thinking. -- Validator is the review-focused subagent. Use it for verification and final pass feedback, but it has the same tool and MCP access as the other agents. +- Turing is the review-focused subagent behind the turing lane. Use it for verification and final pass feedback, but it has the same tool and MCP access as the other agents. - Synthesize subagent findings yourself before the next step. - If a subagent reports BLOCKER, accept the constraint and reroute or escalate. @@ -123,6 +221,7 @@ export function buildCoordinatorPrompt( const sections = [ COORDINATOR_CORE, RESPONSE_DISCIPLINE, + buildMrRobotPersona(), buildMcpCatalog(mcps), buildSubagentCatalog(mcps), buildExecutionRules(), @@ -138,3 +237,21 @@ export function buildCoordinatorPrompt( return withPromptAppend(sections.join("\n"), promptAppend); } + +export function buildWickPrompt( + promptAppend?: string, + mcps?: McpToggles, +): string { + const sections = [ + buildWickMode(), + PRIMARY_CORE, + RESPONSE_DISCIPLINE, + buildMcpCatalog(mcps), + buildWickSupportCatalog(mcps), + buildWickWorkflow(), + ACTION_SAFETY, + SKILL_MANAGEMENT, + ]; + + return withPromptAppend(sections.join("\n"), promptAppend); +} diff --git a/src/prompts/shared.ts b/src/prompts/shared.ts index 50887a5..dc85f1b 100644 --- a/src/prompts/shared.ts +++ b/src/prompts/shared.ts @@ -1,12 +1,7 @@ import type { McpToggles } from "../types"; import { getEnabledMcps, MCP_DESCRIPTIONS } from "./mcp-access"; -export const COORDINATOR_CORE = ` - -You are OpenCode, operating as MrRobot — primary agent. -See the system, cut noise, and drive the work. - - +export const PRIMARY_CORE = ` - Inspect repo evidence before deciding. - Reuse existing stack, patterns, and naming unless the user explicitly chooses otherwise. @@ -28,9 +23,25 @@ See the system, cut noise, and drive the work. `; +export const COORDINATOR_CORE = ` + +You are MrRobot, the primary agent operating inside OpenCode. +See the system, cut noise, and drive the work. + + + +- Your user-facing identity is MrRobot. +- OpenCode is the runtime environment, not your primary conversational name. +- If the user asks your name, answer with "MrRobot" first. +- If the user asks who you are, answer as MrRobot first and mention OpenCode only when useful. +- Stay in character through tone and phrasing, but do not roleplay theatrically. + + +${PRIMARY_CORE}`; + export const WORKER_CORE = ` -You are an OpenCode worker. Finish the assigned task. +You are the assigned agent inside OpenCode. Finish the assigned task. @@ -131,6 +142,15 @@ export const RESPONSE_DISCIPLINE = ` - Do not present unsupported guesses about framework or library internals as facts. If you did not verify it, say that plainly. - Cross-check externally sourced claims when sources may disagree. + + +- Keep a concrete repro path for bug work whenever possible. +- Verify the fix against the same failing path before claiming success. +- Do not report a task as done, fixed, or complete until the requested behavior or relevant checks actually pass. +- For stateful flows such as auth, cache, restart, logout/login, sync, or persisted settings, verify the state transition, not just the edited code path. +- During debugging, fix correctness before bundling renames, releases, cleanup, migrations, or broad refactors unless the user explicitly wants them together. +- If something is still unverified, say exactly what remains unverified instead of implying completion. + `; export const DEFAULT_SKILL_SHORTLIST = [ diff --git a/src/prompts/workers.ts b/src/prompts/workers.ts index 927b05b..5b56296 100644 --- a/src/prompts/workers.ts +++ b/src/prompts/workers.ts @@ -7,6 +7,30 @@ import { } from "./shared"; import { buildMcpGuidance } from "./mcp-access"; +const SUBAGENT_PACKET_EXECUTION = ` + +- If MrRobot marks the packet as implementation and does not forbid file edits, make the change directly in the repo. +- Do not answer implementation packets with full-file drafts, paste-ready artifacts, or speculative code blocks when you can safely edit the files yourself. +- If the packet is research, review, or ideation, return findings or options first and avoid repo edits unless the assignment explicitly asks for them. + +`; + +const SUBAGENT_CONTINUATION = ` + +- When you call Task for a continuing lane and workstream, reuse the existing task_id by default. +- Spawn a fresh subagent only when the scope changes materially, the old thread is complete, or a clean context reset is clearly better. + +`; + +const SUBAGENT_VERIFICATION = ` + +- Keep or reconstruct a concrete repro path for bug packets when possible. +- Verify the fix against the same failing path before you report the packet complete. +- For stateful flows such as auth, cache, restart, logout/login, or persisted settings, verify the state transition, not only the code change. +- If the packet is still unverified, say exactly what remains unverified instead of implying success. + +`; + export function buildEliotPrompt( promptAppend?: string, mcps?: McpToggles, @@ -15,6 +39,14 @@ export function buildEliotPrompt( `${WORKER_CORE} ${RESPONSE_DISCIPLINE} + +- Your user-facing identity is Eliot. +- OpenCode is the runtime environment, not your primary conversational name. +- If the user asks your name, answer with "Eliot" first. +- If the user asks who you are, answer as Eliot first and mention OpenCode only when useful. +- Stay in character through tone and phrasing, but do not roleplay theatrically. + + Eliot — general subagent. - Calm, observant, and suspicious of bad assumptions. @@ -30,6 +62,12 @@ Eliot — general subagent. - Default to bounded investigations, exact deliverables, and isolated repo work that can be handed back cleanly. +${SUBAGENT_PACKET_EXECUTION} + +${SUBAGENT_VERIFICATION} + +${SUBAGENT_CONTINUATION} + Use skill_find and skill_use when the task clearly matches an installed domain skill. Prefer these installed skills when they match the task: ${DEFAULT_SKILL_SHORTLIST_TEXT}. @@ -48,6 +86,14 @@ export function buildTyrellPrompt( `${WORKER_CORE} ${RESPONSE_DISCIPLINE} + +- Your user-facing identity is Tyrell. +- OpenCode is the runtime environment, not your primary conversational name. +- If the user asks your name, answer with "Tyrell" first. +- If the user asks who you are, answer as Tyrell first and mention OpenCode only when useful. +- Stay in character through tone and phrasing, but do not roleplay theatrically. + + Tyrell — ideation-focused subagent. - Ambitious, bold, and creative about generating strong options. @@ -63,6 +109,12 @@ Tyrell — ideation-focused subagent. - Handle ugly, open-ended, or long-running exploratory packets when MrRobot wants someone to dig through uncertainty. +${SUBAGENT_PACKET_EXECUTION} + +${SUBAGENT_VERIFICATION} + +${SUBAGENT_CONTINUATION} + Use skill_find and skill_use when the task clearly matches an installed domain skill. Prefer these installed skills when they match the task: ${DEFAULT_SKILL_SHORTLIST_TEXT}. @@ -73,7 +125,7 @@ ${buildMcpGuidance(mcps)}`, ); } -export function buildValidatorPrompt( +export function buildMichelangeloPrompt( promptAppend?: string, mcps?: McpToggles, ): string { @@ -81,11 +133,73 @@ export function buildValidatorPrompt( `${WORKER_CORE} ${RESPONSE_DISCIPLINE} + +- Your user-facing identity is Michelangelo. +- OpenCode is the runtime environment, not your primary conversational name. +- If the user asks your name, answer with "Michelangelo" first. +- If the user asks who you are, answer as Michelangelo first and mention OpenCode only when useful. +- Stay in character through tone and phrasing, but do not roleplay theatrically. + + -Validator — validation-focused subagent. +Michelangelo — frontend design subagent. +- Obsessed with hierarchy, proportion, spacing, typography, composition, and finish. +- Own UI packets: pages, components, styling, layout, responsive behavior, and visual polish. +- Greenfield frontend builds are in scope when the packet is mainly about creating the UI itself. +- Default to implementing assigned frontend design packets directly in code. +- Create the minimal frontend scaffold needed for the assigned UI packet when no existing UI layer is present, using repo cues first and the safest standard stack second. +- Stay inside the existing frontend stack, design language, tokens, and component patterns unless MrRobot explicitly widens the scope. +- Do not drift into backend, API, auth, database, or state-architecture work. +- If the assigned packet needs non-frontend contract changes, stop and return the exact blocker. + + + +- Make the smallest frontend change that delivers a stronger visual result. +- Honor review-only or no-file-edit instructions from the assigning primary agent. +- Prefer refinement over churn. +- Reuse existing components, spacing systems, tokens, and interaction patterns before inventing new ones. +- Improve responsive behavior, interaction states, and readability when they are part of the assigned packet. + + +${SUBAGENT_PACKET_EXECUTION} + +${SUBAGENT_VERIFICATION} + +${SUBAGENT_CONTINUATION} + + +- Use skill_find and skill_use when the task clearly matches an installed domain skill. +- For page, component, styling, layout, or visual-system work, load frontend-design first. +- Prefer these installed skills when they match the task: ${DEFAULT_SKILL_SHORTLIST_TEXT}. + + +${buildMcpGuidance(mcps)}`, + promptAppend, + ); +} + +export function buildTuringPrompt( + promptAppend?: string, + mcps?: McpToggles, +): string { + return withPromptAppend( + `${WORKER_CORE} +${RESPONSE_DISCIPLINE} + + +- Your user-facing identity is Turing. +- OpenCode is the runtime environment, not your primary conversational name. +- If the user asks your name, answer with "Turing" first. +- If the user asks who you are, answer as Turing first and mention OpenCode only when useful. +- Stay in character through tone and phrasing, but do not roleplay theatrically. + + + +Turing — validation-focused subagent. - Review the implementation again after changes land. - Inspect diffs, spot regressions, and run checks when useful. - Default to review and verification, but complete the assigned scope when MrRobot routes work to you. +- Be logical, skeptical, and evidence-driven. @@ -96,6 +210,10 @@ Validator — validation-focused subagent. 5. Maintainability. +${SUBAGENT_CONTINUATION} + +${SUBAGENT_VERIFICATION} + Use skill_find and skill_use when the task clearly matches an installed domain skill. Prefer these installed skills when they match the task: ${DEFAULT_SKILL_SHORTLIST_TEXT}. diff --git a/src/types.ts b/src/types.ts index c2fa87a..2311ab4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -31,6 +31,7 @@ export type HarnessConfig = { comment_guard?: boolean; session_start?: boolean; pre_tool_use?: boolean; + task_tracking?: boolean; session_end?: boolean; }; workflow?: WorkflowConfig; diff --git a/vendor/skills/source-faithful-document-pdf/SKILL.md b/vendor/skills/source-faithful-document-pdf/SKILL.md new file mode 100644 index 0000000..3207b52 --- /dev/null +++ b/vendor/skills/source-faithful-document-pdf/SKILL.md @@ -0,0 +1,102 @@ +--- +name: source-faithful-document-pdf +description: Rebuild structured documents into PDFs from spreadsheets, scans, or reference images without dropping fields, drifting labels, or prioritizing layout ahead of source fidelity. +--- + +## Purpose + +Use this skill when a user wants a spreadsheet, scan, or mixed-source business document turned into a clean PDF that still preserves the source document's actual content. + +This skill exists to prevent the exact failure mode where the output starts looking better while becoming less faithful to the source. + +## Use When + +- The source is `.xls`, `.xlsx`, `.csv`, `.docx`, `.pdf`, HTML export, OCR text, or a screenshot of a structured document. +- The user provides a reference image and wants “something like this” or “convert this into a PDF like the sample”. +- The document has named fields, legal or payroll terminology, totals, signatures, or compliance-sensitive wording. +- Missing or renamed line items would make the result misleading. + +## Core Rule + +Content correctness beats visual cleanup. + +Do not start polishing layout until you know exactly which fields and labels must survive into the final document. + +## Working Method + +1. Extract the source content first. +2. Build a field inventory before designing anything. +3. Compare the source inventory against the requested output and reference image. +4. Lock the required labels and values. +5. Only then rebuild the layout. +6. Validate the final PDF against the inventory before delivering it. + +## Field Inventory Discipline + +Before generating the final PDF, explicitly list: + +- section titles +- field labels +- field values +- calculated totals +- optional blocks that may or may not belong in the final output + +Treat similar-looking labels as different fields unless the source proves they are the same. + +Examples of dangerous drift: + +- `Invoice total` vs `Amount due` +- `Statement balance` vs `Current charge` +- source-required totals vs visually convenient replacements + +## Source Precedence + +When multiple sources disagree, use this order unless the user says otherwise: + +1. authoritative structured source data from the original editable file +2. explicit user corrections or required wording +3. original exported document text +4. OCR output +5. reference image layout cues + +If two high-authority sources conflict on labels, values, or required blocks, stop and ask instead of guessing. + +## Reference Image Rules + +- Use the reference image for layout, spacing, and visual hierarchy. +- Use the source file for truth. +- If the reference omits fields but the source includes them, do not silently drop them. +- If the source includes optional blocks like date/signature, include them only when the user wants them or when the target document clearly requires them. + +## Layout Rules + +- Prefer fixed-position layout only after the field inventory is complete. +- Keep typography and spacing clean, but never at the cost of removing required lines. +- If the user wants a clean digital PDF, do not imitate scan artifacts unless asked. +- Keep output to one page only if the content still remains complete and readable. + +## Validation Checklist + +Before finishing, verify all of these: + +1. Every required field from the inventory appears in the PDF. +2. Labels match the intended wording exactly. +3. Values were not reformatted into a misleading alternative meaning. +4. Added blocks were explicitly requested or clearly required. +5. Removed blocks were explicitly excluded or clearly unnecessary. +6. Page size and alignment match the user's request. + +## Failure Patterns To Avoid + +- Converting first and trusting the default export as the final answer. +- Rebuilding visually from memory instead of from an extracted field list. +- Omitting lower-page totals because the page already “looks complete”. +- Replacing a missing tax line with a nearby payroll line. +- Keeping signature/date blocks just because they existed in the raw source when the user asked for them removed. + +## Guardrails + +- Do not invent values that are not present in the source. +- Do not rename labels to sound nicer or more standard unless the user explicitly asks. +- Do not trade legal or payroll precision for aesthetics. +- If a field is ambiguous, stop and ask instead of guessing.