tina4-nodejs 3.13.97 → 3.13.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +60 -25
- package/package.json +1 -2
- package/packages/cli/dist/bin.js +20620 -18995
- package/packages/cli/src/bin.ts +28 -71
- package/packages/cli/src/commands/migrate.ts +36 -75
- package/packages/cli/src/commands/migrateRollback.ts +10 -1
- package/packages/cli/src/commands/test.ts +92 -21
- package/packages/core/dist/index.js +20459 -18815
- package/packages/core/public/js/tina4-dev-admin.min.js +23 -19
- package/packages/core/src/ai.ts +28 -12
- package/packages/core/src/api.ts +13 -5
- package/packages/core/src/background.ts +9 -3
- package/packages/core/src/devAdmin.ts +135 -20
- package/packages/core/src/dispatchPipeline.ts +185 -1
- package/packages/core/src/docs.ts +33 -5
- package/packages/core/src/env.ts +1 -1
- package/packages/core/src/errorOverlay.ts +39 -48
- package/packages/core/src/fakeData.ts +15 -0
- package/packages/core/src/index.ts +17 -6
- package/packages/core/src/logger.ts +892 -572
- package/packages/core/src/mcp.ts +9 -1
- package/packages/core/src/messenger.ts +31 -4
- package/packages/core/src/middleware.ts +169 -43
- package/packages/core/src/portTakeover.ts +232 -0
- package/packages/core/src/request.ts +57 -8
- package/packages/core/src/response.ts +67 -0
- package/packages/core/src/router.ts +35 -7
- package/packages/core/src/server.ts +450 -190
- package/packages/core/src/static.ts +81 -12
- package/packages/core/src/testClient.ts +126 -137
- package/packages/core/src/testing.ts +16 -12
- package/packages/core/src/types.ts +21 -9
- package/packages/core/src/version.ts +66 -0
- package/packages/core/src/websocket.ts +2 -2
- package/packages/core/src/websocketBackplane.ts +2 -2
- package/packages/frond/dist/index.js +31 -13
- package/packages/frond/src/engine.ts +39 -7
- package/packages/orm/dist/index.js +10879 -9258
- package/packages/orm/src/adapters/firebird.ts +200 -27
- package/packages/orm/src/adapters/mongodb.ts +160 -10
- package/packages/orm/src/adapters/mssql.ts +38 -11
- package/packages/orm/src/adapters/mysql.ts +24 -1
- package/packages/orm/src/adapters/odbc.ts +127 -29
- package/packages/orm/src/adapters/postgres.ts +18 -0
- package/packages/orm/src/adapters/sqlite.ts +93 -14
- package/packages/orm/src/autoCrud.ts +72 -8
- package/packages/orm/src/baseModel.ts +323 -71
- package/packages/orm/src/cachedDatabase.ts +48 -1
- package/packages/orm/src/database.ts +162 -59
- package/packages/orm/src/fakeData.ts +6 -2
- package/packages/orm/src/index.ts +4 -1
- package/packages/orm/src/migration.ts +95 -52
- package/packages/orm/src/query.ts +16 -4
- package/packages/orm/src/seeder.ts +43 -25
- package/packages/orm/src/sqlTranslator.ts +104 -19
- package/packages/orm/src/types.ts +97 -21
- package/packages/orm/src/validation.ts +5 -1
- package/packages/swagger/dist/index.js +3 -2
- package/packages/swagger/src/generator.ts +19 -4
- package/packages/swagger/src/ui.ts +6 -4
- package/types/cli/src/bin.d.ts +0 -22
- package/types/core/src/api.d.ts +11 -4
- package/types/core/src/background.d.ts +5 -2
- package/types/core/src/devAdmin.d.ts +35 -0
- package/types/core/src/dispatchPipeline.d.ts +41 -1
- package/types/core/src/errorOverlay.d.ts +13 -13
- package/types/core/src/index.d.ts +9 -6
- package/types/core/src/logger.d.ts +111 -185
- package/types/core/src/middleware.d.ts +40 -5
- package/types/core/src/portTakeover.d.ts +50 -0
- package/types/core/src/request.d.ts +15 -0
- package/types/core/src/response.d.ts +29 -0
- package/types/core/src/server.d.ts +92 -0
- package/types/core/src/testClient.d.ts +29 -3
- package/types/core/src/testing.d.ts +16 -12
- package/types/core/src/types.d.ts +21 -9
- package/types/core/src/version.d.ts +11 -0
- package/types/core/src/websocketBackplane.d.ts +1 -1
- package/types/frond/src/engine.d.ts +10 -0
- package/types/orm/src/adapters/firebird.d.ts +61 -2
- package/types/orm/src/adapters/mongodb.d.ts +20 -0
- package/types/orm/src/adapters/mssql.d.ts +11 -0
- package/types/orm/src/adapters/mysql.d.ts +11 -0
- package/types/orm/src/adapters/odbc.d.ts +35 -4
- package/types/orm/src/adapters/postgres.d.ts +11 -0
- package/types/orm/src/adapters/sqlite.d.ts +23 -4
- package/types/orm/src/baseModel.d.ts +45 -25
- package/types/orm/src/cachedDatabase.d.ts +27 -1
- package/types/orm/src/database.d.ts +56 -6
- package/types/orm/src/index.d.ts +3 -2
- package/types/orm/src/migration.d.ts +23 -5
- package/types/orm/src/query.d.ts +3 -0
- package/types/orm/src/seeder.d.ts +15 -2
- package/types/orm/src/sqlTranslator.d.ts +17 -4
- package/types/orm/src/types.d.ts +75 -16
- package/packages/core/src/errorOverlay.test.ts +0 -122
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var n2=Object.defineProperty;var r2=(Ei,L,Q)=>L in Ei?n2(Ei,L,{enumerable:!0,configurable:!0,writable:!0,value:Q}):Ei[L]=Q;var xa=(Ei,L,Q)=>r2(Ei,typeof L!="symbol"?L+"":L,Q);(function(){"use strict";const Ei="/__dev/api";async function L(i,e="GET",t){const n={method:e,headers:{}};t&&(n.headers["Content-Type"]="application/json",n.body=JSON.stringify(t));const r=await fetch(Ei+i,n),s=await r.text();let o;if(s)try{o=JSON.parse(s)}catch{o=s}if(!r.ok){const a=o&&typeof o=="object"?o.error||o.message||JSON.stringify(o):typeof o=="string"&&o?o.slice(0,200):"";throw new Error(a?`${r.status}: ${a}`:`HTTP ${r.status}`)}return o}function Q(i){const e=document.createElement("span");return e.textContent=i,e.innerHTML}const kd={python:{color:"#3b82f6",name:"Python"},php:{color:"#8b5cf6",name:"PHP"},ruby:{color:"#ef4444",name:"Ruby"},nodejs:{color:"#22c55e",name:"Node.js"}};function oy(){const i=document.getElementById("app"),e=(i==null?void 0:i.dataset.framework)??"python",t=i==null?void 0:i.dataset.color,n=kd[e]??kd.python;return{framework:e,color:t??n.color,name:n.name}}function ay(i){const e=document.documentElement;e.style.setProperty("--primary",i.color),e.style.setProperty("--bg","#0f172a"),e.style.setProperty("--surface","#1e293b"),e.style.setProperty("--border","#334155"),e.style.setProperty("--text","#e2e8f0"),e.style.setProperty("--muted","#94a3b8"),e.style.setProperty("--success","#22c55e"),e.style.setProperty("--danger","#ef4444"),e.style.setProperty("--warn","#f59e0b"),e.style.setProperty("--info","#3b82f6")}const ly=`
|
|
2
2
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
3
3
|
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: var(--bg); color: var(--text); }
|
|
4
4
|
|
|
@@ -580,7 +580,7 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
|
|
|
580
580
|
</div>
|
|
581
581
|
</div>
|
|
582
582
|
</div>
|
|
583
|
-
`,UZ(),
|
|
583
|
+
`,UZ(),Nz(),Ue(".").then(()=>ZZ()),Mi("plan_current",{}).then(e=>{$a(e.ok&&e.result||null)}),EQ(),vQ(),Sz(bz()),Pz(),GQ(),vz().catch(()=>{});try{localStorage.removeItem("tina4.editor.chatHistory.v1")}catch{}}const TQ="tina4.editor.state";function nd(){try{const i={openPaths:ee.map(e=>e.path),activeFile:B,activeDir:ss,expandedDirs:Array.from(fi)};localStorage.setItem(TQ,JSON.stringify(i))}catch{}}async function ZZ(){let i;try{i=JSON.parse(localStorage.getItem(TQ)||"{}")}catch{return}for(const e of i.expandedDirs||[])fi.add(e),Kn.has(e)||await Ue(e);i.activeDir&&(ss=i.activeDir);for(const e of i.openPaths||[])try{await ui(e)}catch{}i.activeFile&&ee.some(e=>e.path===i.activeFile)&&ls(i.activeFile),os()}async function Ue(i){try{const e=await L(`/files?path=${encodeURIComponent(i)}`);if(e.branch){const t=document.getElementById("editor-branch");t&&(t.textContent=`⎇ ${e.branch}`)}Kn.set(i,e.entries||[]),os()}catch(e){console.error("Failed to load file tree:",e)}}function os(){const i=document.getElementById("editor-file-tree");i&&(i.innerHTML=XQ(".",0))}async function zZ(){const i=document.getElementById("editor-file-tree");if(!i)return;const e=Array.from(new Set([".",...Array.from(Kn.keys()),...Array.from(fi)])),t=await Promise.all(e.map(async o=>{try{const a=await L(`/files?path=${encodeURIComponent(o)}`);if(a!=null&&a.branch){const l=document.getElementById("editor-branch");l&&(l.textContent=`⎇ ${a.branch}`)}return[o,(a==null?void 0:a.entries)||[]]}catch{return null}})),n=new Map;for(const o of t){if(!o)continue;const[a,l]=o;Kn.set(a,l);for(const O of l)n.set(O.path,O.git_status||"clean")}const r=["git-clean","git-untracked","git-modified","git-added","git-deleted"],s=i.querySelectorAll(".tree-item[data-path]");for(const o of Array.from(s)){const a=o.dataset.path;if(!a)continue;const l=n.get(a);l!==void 0&&(o.classList.remove(...r),o.classList.add(`git-${l}`),qZ(o,l))}}function qZ(i,e){const n={untracked:"U",modified:"M",added:"A",deleted:"D"}[e];let r=i.querySelector(".tree-git-dot");if(!n){r&&r.remove();return}r||(r=document.createElement("span"),r.className="tree-git-dot",i.appendChild(r)),r.title=e,r.textContent=n}let rd=0,as=null;function vQ(){const e=`${location.protocol==="https:"?"wss":"ws"}://${location.host}/__dev_reload`;try{as=new WebSocket(e),as.addEventListener("message",t=>{try{const n=typeof t.data=="string"?JSON.parse(t.data):null;n&&(n.type==="reload"||n.type==="change")&&od()}catch{od()}}),as.addEventListener("close",()=>{as=null,setTimeout(()=>vQ(),5e3)})}catch{as=null}setInterval(async()=>{try{const t=await fetch("/__dev/api/mtime");if(!t.ok)return;const n=await t.json(),r=typeof n.mtime=="number"?n.mtime:0;r>rd&&(rd>0&&od(),rd=r)}catch{}},3e3)}let sd=null;function od(){sd===null&&(sd=window.setTimeout(async()=>{sd=null,await zZ().catch(()=>{})},300))}function XQ(i,e){const t=Kn.get(i);if(!t)return"";fi.has(i);let n="";const r=[...t].sort((s,o)=>s.is_dir!==o.is_dir?s.is_dir?-1:1:s.name.localeCompare(o.name));for(const s of r){const o=`git-${s.git_status||"clean"}`,a=e*16,l=Q(s.path);if(s.is_dir){const O=fi.has(s.path),d=s.has_children!==!1?O?"▾":"▸":" ",h=_Q(s.git_status),f=ss===s.path;n+=`<div class="tree-item tree-dir ${o} ${f?"active":""}" style="padding-left:${a}px"
|
|
584
584
|
data-path="${l}"
|
|
585
585
|
onclick="window.__editorToggleDir('${l}')"
|
|
586
586
|
oncontextmenu="event.preventDefault();window.__editorCtxMenu(event,'${l}',true)">
|
|
@@ -972,7 +972,7 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
|
|
|
972
972
|
<span style="opacity:0.6">${t}/${n}${s?` · ${s}%`:""}</span>
|
|
973
973
|
</span>
|
|
974
974
|
<button class="btn btn-sm" onclick="window.__editorPlanRun('${Q(i.current)}')" ${o?"disabled":""} title="Hand this plan to the Rust supervisor and let it execute remaining steps" style="font-size:0.65rem;padding:1px 6px;line-height:1;${o?"opacity:0.35;cursor:not-allowed":""}">▶</button>
|
|
975
|
-
</span>`}let mi=null;async function LZ(i){mi==null||mi.abort(),mi=new AbortController;const e=document.getElementById("editor-ai-messages");if(!e)return;const t=document.createElement("div");t.className="ai-msg ai-bot",t.style.cssText="padding:0.4rem 0.6rem;font-size:0.72rem;border-left:2px solid var(--info,#89b4fa);background:rgba(137,180,250,0.08)",t.innerHTML=`▶ <strong>Executing plan</strong> <code>plan/${Q(i)}</code> via Rust supervisor… <button class="btn btn-sm" onclick="window.__editorPlanStop()" style="font-size:0.6rem;padding:1px 6px;margin-left:0.5rem">Stop</button>`,e.appendChild(t);const n=document.createElement("div");n.className="ai-msg ai-bot",n.style.cssText="padding:0.35rem 0.6rem;font-size:0.7rem;opacity:0.8;font-family:var(--font-mono,monospace)",n.textContent="starting…",e.appendChild(n);const r=[];try{await VS(`plan/${i}`,o=>{var a;if(o.event==="status")n.textContent=`[${o.agent||"…"}] ${o.text||""}`;else if(o.event==="message"){const l=document.createElement("div");l.className="ai-msg ai-bot",l.style.cssText="padding:0.45rem 0.6rem;font-size:0.75rem";const O=(o.content||"").replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/\n/g,"<br>");l.innerHTML=O,e.appendChild(l),(a=o.files_changed)!=null&&a.length&&r.push(...o.files_changed)}else o.event==="error"&&(n.textContent=`✗ ${o.text||"agent error"}`,n.style.color="var(--danger,#f38ba8)");e.scrollTop=e.scrollHeight},{signal:mi.signal}),n.textContent=r.length?`✓ done — ${r.length} file${r.length===1?"":"s"} touched`:"✓ done",n.style.color="var(--success,#a6e3a1)",await Ue(".");const s=await Mi("plan_current",{});$a(s.ok&&s.result||null)}catch(s){(s==null?void 0:s.name)==="AbortError"?n.textContent="⏹ stopped by user":(n.textContent=`✗ ${(s==null?void 0:s.message)||s}`,n.style.color="var(--danger,#f38ba8)")}finally{mi=null}}function IZ(){mi==null||mi.abort()}const CQ="tina4.editor.dismissedThoughts";function YQ(){try{const i=localStorage.getItem(CQ);if(!i)return new Set;const e=JSON.parse(i);return new Set(Array.isArray(e)?e:[])}catch{return new Set}}function GZ(i){const e=Array.from(i).slice(-200);try{localStorage.setItem(CQ,JSON.stringify(e))}catch{}}const DZ=new Set(["hey","hi","hello","there","team","folks","everyone","i","we","you","they","our","us","my","your","their","noticed","notice","saw","see","spotted","found","thought","that","this","these","those","it","its","the","a","an","and","or","but","for","of","to","in","on","at","with","as","by","from","have","has","had","having","is","are","was","were","be","been","being","do","does","did","does","done","can","could","should","would","will","may","might","must","so","just","even","still","also","too","not","no","yes","project","projects","file","files","new","old","make","makes","made","making","know","knows","knew","known","tricky","bit","exactly","some","any","all","each","every","what","which","why","how","when","where","who","if","then","else","than","because","set","up","down","over","under","out","into","onto","developer","developers","dev","devs","user","users","environment","env"]);function WQ(i){const e=(i||"").toLowerCase().replace(/[^a-z0-9._/-]+/g," ").split(/\s+/).map(s=>s.replace(/^[._\-/]+|[._\-/]+$/g,"")).filter(s=>s.length>1),t=e.filter(s=>s.includes(".")||s.includes("/"));if(t.length)return Array.from(new Set(t)).sort().slice(0,6).join("|");const n=e.filter(s=>s.length>2&&!DZ.has(s));return Array.from(new Set(n)).sort().slice(0,4).join("|")}async function EQ(){const i=document.getElementById("editor-thoughts-banner"),e=document.getElementById("thoughts-empty-state"),t=document.getElementById("thoughts-toolbar"),n=document.getElementById("thoughts-toolbar-count");if(!i)return;let r=[];try{r=await CS()}catch{r=[]}const s=YQ(),o=new Set,a=[];for(const l of r){const O=WQ(l.message);s.has(O)||o.has(O)||(o.add(O),a.push(l))}if(
|
|
975
|
+
</span>`}let mi=null;async function LZ(i){mi==null||mi.abort(),mi=new AbortController;const e=document.getElementById("editor-ai-messages");if(!e)return;const t=document.createElement("div");t.className="ai-msg ai-bot",t.style.cssText="padding:0.4rem 0.6rem;font-size:0.72rem;border-left:2px solid var(--info,#89b4fa);background:rgba(137,180,250,0.08)",t.innerHTML=`▶ <strong>Executing plan</strong> <code>plan/${Q(i)}</code> via Rust supervisor… <button class="btn btn-sm" onclick="window.__editorPlanStop()" style="font-size:0.6rem;padding:1px 6px;margin-left:0.5rem">Stop</button>`,e.appendChild(t);const n=document.createElement("div");n.className="ai-msg ai-bot",n.style.cssText="padding:0.35rem 0.6rem;font-size:0.7rem;opacity:0.8;font-family:var(--font-mono,monospace)",n.textContent="starting…",e.appendChild(n);const r=[];try{await VS(`plan/${i}`,o=>{var a;if(o.event==="status")n.textContent=`[${o.agent||"…"}] ${o.text||""}`;else if(o.event==="message"){const l=document.createElement("div");l.className="ai-msg ai-bot",l.style.cssText="padding:0.45rem 0.6rem;font-size:0.75rem";const O=(o.content||"").replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/\n/g,"<br>");l.innerHTML=O,e.appendChild(l),(a=o.files_changed)!=null&&a.length&&r.push(...o.files_changed)}else o.event==="error"&&(n.textContent=`✗ ${o.text||"agent error"}`,n.style.color="var(--danger,#f38ba8)");e.scrollTop=e.scrollHeight},{signal:mi.signal}),n.textContent=r.length?`✓ done — ${r.length} file${r.length===1?"":"s"} touched`:"✓ done",n.style.color="var(--success,#a6e3a1)",await Ue(".");const s=await Mi("plan_current",{});$a(s.ok&&s.result||null)}catch(s){(s==null?void 0:s.name)==="AbortError"?n.textContent="⏹ stopped by user":(n.textContent=`✗ ${(s==null?void 0:s.message)||s}`,n.style.color="var(--danger,#f38ba8)")}finally{mi=null}}function IZ(){mi==null||mi.abort()}const CQ="tina4.editor.dismissedThoughts";function YQ(){try{const i=localStorage.getItem(CQ);if(!i)return new Set;const e=JSON.parse(i);return new Set(Array.isArray(e)?e:[])}catch{return new Set}}function GZ(i){const e=Array.from(i).slice(-200);try{localStorage.setItem(CQ,JSON.stringify(e))}catch{}}const DZ=new Set(["hey","hi","hello","there","team","folks","everyone","i","we","you","they","our","us","my","your","their","noticed","notice","saw","see","spotted","found","thought","that","this","these","those","it","its","the","a","an","and","or","but","for","of","to","in","on","at","with","as","by","from","have","has","had","having","is","are","was","were","be","been","being","do","does","did","does","done","can","could","should","would","will","may","might","must","so","just","even","still","also","too","not","no","yes","project","projects","file","files","new","old","make","makes","made","making","know","knows","knew","known","tricky","bit","exactly","some","any","all","each","every","what","which","why","how","when","where","who","if","then","else","than","because","set","up","down","over","under","out","into","onto","developer","developers","dev","devs","user","users","environment","env"]);function WQ(i){const e=(i||"").toLowerCase().replace(/[^a-z0-9._/-]+/g," ").split(/\s+/).map(s=>s.replace(/^[._\-/]+|[._\-/]+$/g,"")).filter(s=>s.length>1),t=e.filter(s=>s.includes(".")||s.includes("/"));if(t.length)return Array.from(new Set(t)).sort().slice(0,6).join("|");const n=e.filter(s=>s.length>2&&!DZ.has(s));return Array.from(new Set(n)).sort().slice(0,4).join("|")}async function EQ(){const i=document.getElementById("editor-thoughts-banner"),e=document.getElementById("thoughts-empty-state"),t=document.getElementById("thoughts-toolbar"),n=document.getElementById("thoughts-toolbar-count");if(!i)return;let r=[];try{r=await CS()}catch{r=[]}const s=YQ(),o=new Set,a=[];for(const l of r){const O=WQ(l.message);s.has(O)||o.has(O)||(o.add(O),a.push(l))}if(wz("thoughts",a.length||null,a.length>0),!a.length){i.style.display="none",i.innerHTML="",e&&(e.style.display=""),t&&t.setAttribute("hidden","");return}e&&(e.style.display="none"),t&&(t.removeAttribute("hidden"),n&&(n.textContent=`${a.length} observation${a.length===1?"":"s"}`)),i.style.display="flex",i.innerHTML=a.slice(0,20).map(l=>{const O=(l.message||"").slice(0,180);return`
|
|
976
976
|
<div class="editor-thought-chip" data-id="${Q(l.id)}" data-hash="${Q(WQ(l.message))}">
|
|
977
977
|
<span style="opacity:0.7">💡</span>
|
|
978
978
|
<div class="editor-thought-chip-body" onclick="window.__editorThoughtAct('${Q(l.id)}')">${Q(O)}${l.message.length>180?"…":""}</div>
|
|
@@ -1012,28 +1012,32 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
|
|
|
1012
1012
|
<span>severity: ${Q(e.severity||"-")}</span>
|
|
1013
1013
|
</div>
|
|
1014
1014
|
<div style="margin-top:0.3rem">${Q(e.summary||"")}</div>
|
|
1015
|
-
</div>`}catch{return fs(i)}}async function cz(){try{const i=await cs();Pe.push(i),nt.set(i.id,[]),await Sa(i.id)}catch(i){console.error("threadsNew failed",i)}}async function dz(){if(!Re)return;const i=Pe.find(t=>t.id===Re);if(!i)return;const e=window.prompt("Rename thread:",i.title);if(!(e==null||e.trim()===""||e===i.title))try{const t=await Qa(Re,{title:e.trim()});i.title=t.title;const n=document.getElementById("threads-detail-title");n&&(n.textContent=t.title),er()}catch(t){console.error("rename failed",t)}}function hz(i){return i?i.status_hint==="done"||i.status_hint==="wont_do"||i.closure_reason==="done"||i.closure_reason==="wont_do":!1}async function $d(){const i=document.getElementById("threads-reply-input");if(!i)return;let e=i.value.trim();if(!e)return;i.value="";const t=e.match(/^new topic:\s*(.*)$/is);if(t){const o=t[1].trim();try{const a=await cs(o.slice(0,80)||void 0);if(Pe.push(a),nt.set(a.id,[]),await Sa(a.id),!o)return;e=o}catch(a){console.error("New Topic spawn failed",a);return}}if(!t&&Re&&hz(Pe.find(o=>o.id===Re)))try{const o=await cs(e.slice(0,80)||void 0);Pe.push(o),nt.set(o.id,[]),await Sa(o.id)}catch(o){console.error("auto-new-thread on done reply failed",o);return}if(!Re)try{const o=await cs(e.slice(0,80));Pe.push(o),nt.set(o.id,[]),Re=o.id}catch(o){console.error("auto-create failed",o);return}const n=Re,r=nt.get(n)||[];r.push({id:`local-${Date.now()}`,role:"user",content:e,timestamp:new Date().toISOString(),thread_id:n}),nt.set(n,r),md(n),ga.add(n),er();const s=document.getElementById("threads-chat");Jn==null||Jn.abort(),Jn=new AbortController;try{await
|
|
1015
|
+
</div>`}catch{return fs(i)}}async function cz(){try{const i=await cs();Pe.push(i),nt.set(i.id,[]),await Sa(i.id)}catch(i){console.error("threadsNew failed",i)}}async function dz(){if(!Re)return;const i=Pe.find(t=>t.id===Re);if(!i)return;const e=window.prompt("Rename thread:",i.title);if(!(e==null||e.trim()===""||e===i.title))try{const t=await Qa(Re,{title:e.trim()});i.title=t.title;const n=document.getElementById("threads-detail-title");n&&(n.textContent=t.title),er()}catch(t){console.error("rename failed",t)}}function hz(i){return i?i.status_hint==="done"||i.status_hint==="wont_do"||i.closure_reason==="done"||i.closure_reason==="wont_do":!1}async function $d(){const i=document.getElementById("threads-reply-input");if(!i)return;let e=i.value.trim();if(!e)return;i.value="";const t=e.match(/^new topic:\s*(.*)$/is);if(t){const o=t[1].trim();try{const a=await cs(o.slice(0,80)||void 0);if(Pe.push(a),nt.set(a.id,[]),await Sa(a.id),!o)return;e=o}catch(a){console.error("New Topic spawn failed",a);return}}if(!t&&Re&&hz(Pe.find(o=>o.id===Re)))try{const o=await cs(e.slice(0,80)||void 0);Pe.push(o),nt.set(o.id,[]),await Sa(o.id)}catch(o){console.error("auto-new-thread on done reply failed",o);return}if(!Re)try{const o=await cs(e.slice(0,80));Pe.push(o),nt.set(o.id,[]),Re=o.id}catch(o){console.error("auto-create failed",o);return}const n=Re,r=nt.get(n)||[];r.push({id:`local-${Date.now()}`,role:"user",content:e,timestamp:new Date().toISOString(),thread_id:n}),nt.set(n,r),md(n),ga.add(n),er();const s=document.getElementById("threads-chat");Jn==null||Jn.abort(),Jn=new AbortController;try{await Qz(e,n,Jn.signal,s)}catch(o){if((o==null?void 0:o.name)!=="AbortError"){const a=document.createElement("div");a.className="ai-msg ai-bot",a.style.color="var(--danger,#f38ba8)",a.textContent=`Connection failed: ${(o==null?void 0:o.message)||o}`,s==null||s.appendChild(a)}}finally{ga.delete(n),Jn=null;try{await jQ(n,!0)}catch{}await ya(),md(n);const o=Pe.find(l=>l.id===n),a=document.getElementById("threads-detail-meta");if(o&&a){const l=o.sender?`<span>📨 from ${Q(o.sender)}</span>`:"";a.innerHTML=`${fd(o.status_hint||"idle")} <span>${Q(ud(o.last_message_at))}</span> ${l}`}}}queueMicrotask(()=>{const i=document.getElementById("threads-reply-form");i&&i.addEventListener("submit",t=>{t.preventDefault(),$d()});const e=document.getElementById("threads-reply-input");e&&e.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),t.stopPropagation(),$d())})}),window.__threadsShowList=()=>ba(),window.__threadsShowDetail=i=>{Sa(i)},window.__threadsNew=()=>{cz()},window.__threadsRetry=()=>{ya(2).then(er)},window.__threadsRenameActive=()=>{dz()};let un=!1;async function LQ(){const i=document.getElementById("plans-panel"),e=document.getElementById("plans-toggle-btn");i&&(un=!un,i.hidden=!un,e==null||e.classList.toggle("active",un),un&&await fz())}async function fz(){const i=document.getElementById("plans-rows");if(i){i.innerHTML='<div class="threads-empty">Loading plans…</div>';try{const e=await Mi("plan_list",{}),t=e.ok&&Array.isArray(e.result)?e.result:[];if(!t.length){i.innerHTML='<div class="threads-empty">No plans yet — they appear here when the planner agent creates one.</div>';return}const n=[...t].sort((r,s)=>String(s.name||s.file||"").localeCompare(String(r.name||r.file||"")));i.innerHTML=n.map(r=>{var h,f;const s=String(r.name||r.file||""),o=String(r.path||`plan/${s}`),a=String(r.title||s).slice(0,60),l=r.steps_done??((h=r.progress)==null?void 0:h.done)??0,O=r.steps_total??((f=r.progress)==null?void 0:f.total)??0,c=O>0?`${l}/${O} steps`:"",d=r.is_current||r.current?" · ★ current":"";return`<div class="plan-row" onclick="window.__plansOpen('${Q(o)}')" title="Open ${Q(o)} in editor">
|
|
1016
1016
|
<div class="plan-name">${Q(a)}</div>
|
|
1017
1017
|
<div class="plan-meta">${Q(s)}${c?" · "+Q(c):""}${d}</div>
|
|
1018
|
-
</div>`}).join("")}catch{i.innerHTML='<div class="threads-empty" style="color:var(--danger,#f38ba8)">Failed to load plans</div>'}}}async function uz(i){var t;try{await ui(i)}catch(n){console.error("plansOpen failed",n)}un=!1;const e=document.getElementById("plans-panel");e&&(e.hidden=!0),(t=document.getElementById("plans-toggle-btn"))==null||t.classList.remove("active")}window.__plansToggle=()=>{LQ()},window.__plansOpen=i=>{uz(i)};let ds=!1;async function pz(){const i=document.getElementById("grounding-panel"),e=document.getElementById("grounding-toggle-btn");i&&(un&&LQ(),ds=!ds,i.hidden=!ds,e==null||e.classList.toggle("active",ds),ds&&await IQ())}
|
|
1018
|
+
</div>`}).join("")}catch{i.innerHTML='<div class="threads-empty" style="color:var(--danger,#f38ba8)">Failed to load plans</div>'}}}async function uz(i){var t;try{await ui(i)}catch(n){console.error("plansOpen failed",n)}un=!1;const e=document.getElementById("plans-panel");e&&(e.hidden=!0),(t=document.getElementById("plans-toggle-btn"))==null||t.classList.remove("active")}window.__plansToggle=()=>{LQ()},window.__plansOpen=i=>{uz(i)};let ds=!1;async function pz(){const i=document.getElementById("grounding-panel"),e=document.getElementById("grounding-toggle-btn");i&&(un&&LQ(),ds=!ds,i.hidden=!ds,e==null||e.classList.toggle("active",ds),ds&&await IQ())}function mz(i){const e=Q(i.url||"https://mcp.tina4.com"),t=i.source||(i.configured?"personal":"none");if(t==="personal")return{source:t,stateHtml:`<span style="color:var(--success,#a6e3a1)">● Your token</span> <span style="opacity:0.6">(…${Q(i.last4||"")})</span>`,nudgeHtml:""};if(t==="free"){const n=i.dev_email?` Identified to the server as <code>${Q(i.dev_email)}</code>.`:"";return{source:t,stateHtml:`<span style="color:var(--warn,#f9e2af)">🎁 Free trial</span> — grounding via <code>${e}</code> on the shared <code>FREE-TOKEN</code>.`,nudgeHtml:`<div style="margin:0.4rem 0;padding:0.4rem 0.55rem;border:1px solid var(--warn,#f9e2af);border-radius:6px;background:color-mix(in srgb, var(--warn,#f9e2af) 12%, transparent)">
|
|
1019
|
+
You're trying Tina4 grounding for free.${n} Register for your <strong>own</strong> token — higher limits, no shared rate cap.
|
|
1020
|
+
<a href="https://profile.tina4.com" target="_blank" rel="noopener" style="color:var(--accent,#89b4fa);font-weight:600">Register at profile.tina4.com →</a>
|
|
1021
|
+
</div>`}}return{source:t,stateHtml:'<span style="color:var(--warn,#f9e2af)">○ Not set</span> — using local corpus fallback',nudgeHtml:""}}async function IQ(){const i=document.getElementById("grounding-body");if(!i)return;i.innerHTML='<div class="threads-empty">Loading…</div>';let e={};try{const s=await fetch("/__dev/api/grounding/status");s.ok&&(e=await s.json())}catch{}const t=Q(e.url||"https://mcp.tina4.com"),{stateHtml:n,nudgeHtml:r}=mz(e);i.innerHTML=`
|
|
1019
1022
|
<div style="font-weight:600;margin-bottom:0.35rem">Framework grounding</div>
|
|
1020
1023
|
<div style="opacity:0.85;margin-bottom:0.5rem">Ground the coder against <code>${t}</code> (version-current Tina4 API) instead of the local fallback.</div>
|
|
1021
1024
|
<div style="margin-bottom:0.5rem">${n}</div>
|
|
1025
|
+
${r}
|
|
1022
1026
|
<div style="display:flex;gap:4px">
|
|
1023
|
-
<input type="password" id="grounding-token-input" class="input" placeholder="Paste TINA4_MCP_TOKEN…"
|
|
1027
|
+
<input type="password" id="grounding-token-input" class="input" placeholder="Paste your own TINA4_MCP_TOKEN…"
|
|
1024
1028
|
style="flex:1;font-size:0.72rem;padding:4px 8px;height:28px" autocomplete="off" />
|
|
1025
1029
|
<button type="button" class="btn btn-sm btn-primary" style="font-size:0.65rem;padding:2px 10px"
|
|
1026
1030
|
onclick="window.__groundingSave()">Save</button>
|
|
1027
1031
|
</div>
|
|
1028
1032
|
<div id="grounding-result" style="margin-top:0.4rem;min-height:1.1em"></div>
|
|
1029
1033
|
<div style="opacity:0.6;margin-top:0.4rem">Get a free token at <code>profile.tina4.com</code>. Stored in project <code>.env</code>; takes effect next turn.</div>
|
|
1030
|
-
`}async function
|
|
1034
|
+
`}async function $z(){const i=document.getElementById("grounding-token-input"),e=document.getElementById("grounding-result"),t=i==null?void 0:i.value.trim();if(!t){e&&(e.innerHTML='<span style="color:var(--warn,#f9e2af)">Paste a token first.</span>');return}e&&(e.innerHTML='<span style="opacity:0.7">Saving…</span>');try{const n=await fetch("/__dev/api/grounding/token",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:t})}),r=await n.json().catch(()=>({}));n.ok&&r.ok?(i&&(i.value=""),e&&(e.innerHTML=`<span style="color:var(--success,#a6e3a1)">✓ Saved (…${Q(String(r.last4||""))}). Grounding now uses mcp.tina4.com.</span>`),setTimeout(()=>{IQ()},1200)):e&&(e.innerHTML=`<span style="color:var(--danger,#f38ba8)">Failed: ${Q(String(r.error||n.status))}</span>`)}catch{e&&(e.innerHTML='<span style="color:var(--danger,#f38ba8)">Agent unreachable — is <code>tina4 serve</code> running?</span>')}}window.__groundingToggle=()=>{pz()},window.__groundingSave=()=>{$z()};async function gz(i){const e=document.getElementById("threads-chat")||document.getElementById("editor-ai-messages"),t=document.createElement("div");t.className="ai-msg ai-bot";const n=document.createElement("div");n.style.cssText="font-size:0.72rem;opacity:0.85;font-family:var(--font-mono,monospace)",n.textContent="▶ Building…",t.appendChild(n),e==null||e.appendChild(t);const r=i.includes("/")?i:`plan/${i}`;try{const s=await fetch("/__dev/api/execute",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({plan_file:r})});if(!s.ok||!s.body){n.textContent=`✗ execute ${s.status}`;return}const o=s.body.getReader(),a=new TextDecoder;let l="";for(;;){const{done:O,value:c}=await o.read();if(O)break;l+=a.decode(c,{stream:!0});let d;for(;(d=l.indexOf(`
|
|
1031
1035
|
|
|
1032
1036
|
`))!==-1;){const h=l.slice(0,d);l=l.slice(d+2);let f="message",p="";for(const m of h.split(`
|
|
1033
|
-
`))m.startsWith("event:")?f=m.slice(6).trim():m.startsWith("data:")&&(p+=m.slice(5).trim());if(p){try{const m=JSON.parse(p);if(f==="status")n.textContent=`[${m.agent||"…"}] ${m.text||""}`;else if(f==="message"&&m.content){const $=document.createElement("div");$.style.cssText="font-size:0.75rem;margin-top:0.25rem",$.innerHTML=Q(String(m.content)).replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/\\n|\n/g,"<br>"),t.appendChild($)}}catch{}e&&(e.scrollTop=e.scrollHeight)}}}n.textContent="✓ Build complete — check the file tree / run the app"}catch(s){n.textContent=`✗ ${(s==null?void 0:s.message)||s}`}}window.__buildPlanNow=i=>{
|
|
1037
|
+
`))m.startsWith("event:")?f=m.slice(6).trim():m.startsWith("data:")&&(p+=m.slice(5).trim());if(p){try{const m=JSON.parse(p);if(f==="status")n.textContent=`[${m.agent||"…"}] ${m.text||""}`;else if(f==="message"&&m.content){const $=document.createElement("div");$.style.cssText="font-size:0.75rem;margin-top:0.25rem",$.innerHTML=Q(String(m.content)).replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/\\n|\n/g,"<br>"),t.appendChild($)}}catch{}e&&(e.scrollTop=e.scrollHeight)}}}n.textContent="✓ Build complete — check the file tree / run the app"}catch(s){n.textContent=`✗ ${(s==null?void 0:s.message)||s}`}}window.__buildPlanNow=i=>{gz(i)},window.__threadsArchiveActive=()=>{sz()},window.__threadsArchiveFromList=i=>{oz(i)},queueMicrotask(()=>{nz()});async function Qz(i,e,t,n){const r=n??document.getElementById("threads-chat")??document.getElementById("editor-ai-messages");if(!r)return;const s=document.createElement("div");s.className="ai-msg ai-bot";const o=document.createElement("div");o.style.cssText="font-size:0.72rem;opacity:0.7;margin-bottom:0.3rem;font-family:var(--font-mono,monospace)",o.innerHTML='<span style="opacity:0.6">→ supervisor: thinking…</span>';const a=document.createElement("div");s.appendChild(o),s.appendChild(a),r.appendChild(s),r.scrollTop=r.scrollHeight;const l=(()=>{if(!B)return null;const p=ee.find($=>$.path===B);if(!p)return null;const m=p.content||"";return m.length>6e4?{path:p.path,language:p.language,content:"<too large to inline — ask the user or call file_read>"}:{path:p.path,language:p.language,content:m}})(),O=await fetch("/__dev/api/chat",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:i,thread_id:e,active_file:l}),signal:t});if(!O.ok){let p="";try{p=(await O.text()).slice(0,400)}catch{}o.remove(),a.innerHTML=`<span style="color:var(--danger,#f38ba8)">Supervisor unavailable (HTTP ${O.status}).</span>`+(p?`<pre style="font-size:0.7rem;opacity:0.7;margin-top:0.3rem;white-space:pre-wrap">${Q(p)}</pre>`:"")+'<div style="font-size:0.75rem;opacity:0.7;margin-top:0.3rem">Run <code>tina4 serve</code> (auto-spawns the agent) or set <code>TINA4_SUPERVISOR_URL</code>.</div>';return}if(!O.body){o.remove(),a.innerHTML='<span style="color:var(--danger,#f38ba8)">Supervisor returned no body.</span>';return}const c=O.body.getReader(),d=new TextDecoder;let h="",f=[];for(;;){const{value:p,done:m}=await c.read();if(m)break;h+=d.decode(p,{stream:!0});let $;for(;($=h.indexOf(`
|
|
1034
1038
|
|
|
1035
1039
|
`))!==-1;){const g=h.slice(0,$);h=h.slice($+2);let y="message",S="";for(const w of g.split(`
|
|
1036
|
-
`))w.startsWith("event:")?y=w.slice(6).trim():w.startsWith("data:")&&(S+=w.slice(5).trim());if(!S)continue;let x;try{x=JSON.parse(S)}catch{x={text:S,content:S}}if(y==="status"){const w=String(x.agent||"supervisor"),R=String(x.text||"");o.innerHTML=`<span style="color:var(--info,#89b4fa)">[${Q(w)}]</span> ${Q(R)}`,x.backup&&(o.innerHTML+=` <span style="opacity:0.5">(backup: ${Q(String(x.backup))})</span>`)}else if(y==="message"){const w=String(x.agent||"supervisor"),R=String(x.content||"");o.innerHTML=`<span style="opacity:0.6">↳ ${Q(w)}</span>`,a.innerHTML=fs(R);const _=Array.isArray(x.suggested_replies)?x.suggested_replies.map(A=>String(A)).filter(Boolean):[];s.querySelectorAll(".action-pills").forEach(A=>A.remove()),_.length&&pd(s,_),Array.isArray(x.files_changed)&&(f=x.files_changed.map(A=>String(A)))}else if(y==="plan"){const w=String(x.content||""),R=String(x.file||"");if(o.innerHTML=`<span style="opacity:0.6">↳ planner</span> · plan saved to <code>${Q(R)}</code>`,a.innerHTML=fs(w),x.approve!==!1){if(s.querySelectorAll(".action-pills").forEach(_=>_.remove()),R){const _=document.createElement("div");_.className="action-pills";const A=document.createElement("button");A.type="button",A.className="action-pill",A.style.cssText="background:rgba(166,227,161,0.16);border-color:#a6e3a1;font-weight:600",A.textContent="⚡ Build it now",A.addEventListener("click",()=>{A.setAttribute("disabled",""),window.__buildPlanNow(R)}),_.appendChild(A),s.appendChild(_)}pd(s,["Make changes","Cancel"])}}else if(y==="error"){const w=String(x.message||"Supervisor error");o.innerHTML='<span style="color:var(--danger,#f38ba8)">✗ error</span>',a.innerHTML=`<span style="color:var(--danger,#f38ba8)">${Q(w)}</span>`}r.scrollTop=r.scrollHeight}}for(const p of f)try{await
|
|
1040
|
+
`))w.startsWith("event:")?y=w.slice(6).trim():w.startsWith("data:")&&(S+=w.slice(5).trim());if(!S)continue;let x;try{x=JSON.parse(S)}catch{x={text:S,content:S}}if(y==="status"){const w=String(x.agent||"supervisor"),R=String(x.text||"");o.innerHTML=`<span style="color:var(--info,#89b4fa)">[${Q(w)}]</span> ${Q(R)}`,x.backup&&(o.innerHTML+=` <span style="opacity:0.5">(backup: ${Q(String(x.backup))})</span>`)}else if(y==="message"){const w=String(x.agent||"supervisor"),R=String(x.content||"");o.innerHTML=`<span style="opacity:0.6">↳ ${Q(w)}</span>`,a.innerHTML=fs(R);const _=Array.isArray(x.suggested_replies)?x.suggested_replies.map(A=>String(A)).filter(Boolean):[];s.querySelectorAll(".action-pills").forEach(A=>A.remove()),_.length&&pd(s,_),Array.isArray(x.files_changed)&&(f=x.files_changed.map(A=>String(A)))}else if(y==="plan"){const w=String(x.content||""),R=String(x.file||"");if(o.innerHTML=`<span style="opacity:0.6">↳ planner</span> · plan saved to <code>${Q(R)}</code>`,a.innerHTML=fs(w),x.approve!==!1){if(s.querySelectorAll(".action-pills").forEach(_=>_.remove()),R){const _=document.createElement("div");_.className="action-pills";const A=document.createElement("button");A.type="button",A.className="action-pill",A.style.cssText="background:rgba(166,227,161,0.16);border-color:#a6e3a1;font-weight:600",A.textContent="⚡ Build it now",A.addEventListener("click",()=>{A.setAttribute("disabled",""),window.__buildPlanNow(R)}),_.appendChild(A),s.appendChild(_)}pd(s,["Make changes","Cancel"])}}else if(y==="error"){const w=String(x.message||"Supervisor error");o.innerHTML='<span style="color:var(--danger,#f38ba8)">✗ error</span>',a.innerHTML=`<span style="color:var(--danger,#f38ba8)">${Q(w)}</span>`}r.scrollTop=r.scrollHeight}}for(const p of f)try{await Vz(p)}catch{}}const yz="tina4.editor.session.mode";function bz(){return localStorage.getItem(yz)==="qa"?"qa":"supervisor"}function Sz(i){const e=document.getElementById("session-mode-toggle");if(!e)return;e.querySelectorAll(".mode-btn").forEach(n=>{n.classList.toggle("active",n.dataset.mode===i)});const t=document.getElementById("editor-ai-input");t&&(t.placeholder=i==="qa"?"Ask a question…":"Describe the change…")}function wz(i,e,t=!1){const n=document.getElementById(`tab-badge-${i}`),r=document.querySelector(`.session-tab[data-tab="${i}"]`);if(n){if(!e){n.textContent="",r==null||r.classList.remove("has-alert");return}n.textContent=String(e),r==null||r.classList.toggle("has-alert",t)}}function GQ(){const i=document.getElementById("completion-toggle");if(i){if(i.classList.remove("disabled","on-plan","off-plan"),!VQ){i.classList.add("disabled"),i.setAttribute("title","Completion off — click to enable");return}Os?(i.classList.add("on-plan"),i.setAttribute("title",`Completion on-plan: ${Os.slice(0,80)}`)):(i.classList.add("off-plan"),i.setAttribute("title","Completion on (off-plan — no intent boost)"))}}let DQ=null;async function BQ(){const i=[{key:"chat",url:tr.chat.endpoint},{key:"vision",url:tr.vision.endpoint},{key:"embed",url:tr.embed.endpoint},{key:"image",url:tr.image.endpoint},{key:"rag",url:tr.rag.endpoint}];await Promise.all(i.map(async({key:e,url:t})=>{const n=await Wy(t),r=document.querySelector(`.model-dot[data-model="${e}"]`);r&&(r.classList.toggle("up",n),r.classList.toggle("down",!n))}))}function Pz(){DQ||(BQ(),DQ=window.setInterval(BQ,3e4))}function NQ(i,e){const t=document.getElementById("session-title"),n=document.getElementById("session-meta");t&&i!==null&&(t.textContent=i),n&&(n.textContent=e||"")}let It=null,re=null;const gd="tina4.editor.session.id";function xz(i){It=i,localStorage.removeItem(gd),Qd(),yd()}function Qd(){if(!It){NQ("No active session","");return}const i=(re==null?void 0:re.files.length)??0,e=(re==null?void 0:re.commits.length)??0,t=(re==null?void 0:re.warnings.length)??0,n=It.title||`session ${It.id.slice(0,8)}`,r=[];e&&r.push(`${e} commit${e===1?"":"s"}`),i&&r.push(`${i} file${i===1?"":"s"}`),t&&r.push(`⚠ ${t}`),NQ(n,r.join(" · "))}function yd(){const i=document.getElementById("btn-session-revise"),e=document.getElementById("btn-session-apply"),t=document.getElementById("btn-session-cancel"),n=!!It,r=((re==null?void 0:re.files.length)??0)>0;i&&(i.disabled=!n),e&&(e.disabled=!r),t&&(t.disabled=!n)}function kz(){const i=document.getElementById("diff-empty-state"),e=document.getElementById("diff-content-area");if(!i||!e)return;if(!It||!re||re.files.length===0){i.style.display="",e.setAttribute("hidden","");return}i.style.display="none",e.removeAttribute("hidden");const t=document.getElementById("diff-summary");if(t){const o=re.files.length,a=re.commits.length;t.innerHTML=`
|
|
1037
1041
|
<strong>${o}</strong> file${o===1?"":"s"} across
|
|
1038
1042
|
<strong>${a}</strong> commit${a===1?"":"s"} on
|
|
1039
1043
|
<span class="diff-sha">${Q(re.branch)}</span>
|
|
@@ -1059,11 +1063,11 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
|
|
|
1059
1063
|
<span class="diff-commit-subject">${Q(a.subject)}</span>
|
|
1060
1064
|
${l?`<span class="diff-commit-agent">${Q(l)}</span>`:""}
|
|
1061
1065
|
</div>
|
|
1062
|
-
`}).join("");s.innerHTML=`<div class="diff-commits-header">commits</div>${o}`}}async function
|
|
1063
|
-
`,1)[0]||"",t=e.match(/^\s*(?:\/\/|#|<!--)\s*([\w./\-]+\.\w+)\s*(?:-->)?\s*$/);return t&&t[1].includes("/")?{path:t[1],rest:i.slice(e.length+1)}:{path:null,rest:i}}function
|
|
1066
|
+
`}).join("");s.innerHTML=`<div class="diff-commits-header">commits</div>${o}`}}async function Tz(){if(!It){re=null,Qd(),yd();return}try{re=await AS(It.id)}catch(i){if(re=null,String(i).includes("not found")){xz(null);return}}Qd(),yd(),Xz(),kz()}async function vz(){const i=localStorage.getItem(gd);if(i){try{const t=(await US()).find(n=>n.id===i);if(t){It=t,await Tz();return}}catch{}localStorage.removeItem(gd)}}function Xz(){const i=document.getElementById("session-summary-strip"),e=document.getElementById("summary-chip-session");if(!i||!e)return;if(!It||!re||re.files.length===0){i.setAttribute("hidden","");return}const t=re.files.length,n=re.commits.length,r=re.warnings.length,s=[`<strong>${t}</strong> file${t===1?"":"s"}`,`<strong>${n}</strong> commit${n===1?"":"s"}`];r>0&&s.push(`<strong style="color:var(--warning,#f9e2af)">⚠ ${r}</strong>`),e.innerHTML=s.join(" · "),i.removeAttribute("hidden")}let _z=0;const hs=new Map;function Rz(i){const e=i.split(`
|
|
1067
|
+
`,1)[0]||"",t=e.match(/^\s*(?:\/\/|#|<!--)\s*([\w./\-]+\.\w+)\s*(?:-->)?\s*$/);return t&&t[1].includes("/")?{path:t[1],rest:i.slice(e.length+1)}:{path:null,rest:i}}function Zz(i){const e=l=>Q(l).replace(/`([^`]+)`/g,'<code style="background:rgba(0,0,0,0.3);padding:0.1rem 0.3rem;border-radius:0.2rem">$1</code>').replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/(^|[^*])\*([^*\n]+)\*/g,"$1<em>$2</em>"),t=i.split(`
|
|
1064
1068
|
`),n=[];let r=null,s=!1;const o=()=>{s&&(n.push("</ul>"),s=!1)},a=()=>{o(),r&&(n.push(`</${r}>`),r=null)};for(const l of t){const O=l.replace(/\s+$/,"");if(!O.trim()){a();continue}const c=O.match(/^(#{1,6})\s+(.*)$/);if(c){a(),n.push(`<h${c[1].length} style="margin:0.4rem 0 0.2rem;font-size:${1.05-c[1].length*.05}rem">${e(c[2])}</h${c[1].length}>`);continue}const d=O.match(/^(?:\s{2,}|\t)[-*]\s+(.*)$/);if(d&&r){s||(n.push('<ul style="margin:0.1rem 0 0.1rem 1.2rem">'),s=!0),n.push(`<li>${e(d[1])}</li>`);continue}const h=O.match(/^\s*\d+\.\s+(.*)$/);if(h){r!=="ol"?(a(),n.push('<ol style="margin:0.2rem 0 0.2rem 1.2rem">'),r="ol"):o(),n.push(`<li>${e(h[1])}</li>`);continue}const f=O.match(/^\s*[-*]\s+(.*)$/);if(f){r!=="ul"?(a(),n.push('<ul style="margin:0.2rem 0 0.2rem 1.2rem">'),r="ul"):o(),n.push(`<li>${e(f[1])}</li>`);continue}a(),n.push(`<p style="margin:0.2rem 0">${e(O)}</p>`)}return a(),n.join("")}function fs(i){let e=i.replace(/\\n/g,`
|
|
1065
1069
|
`);return e=e.replace(/```tool_call\s*\n[\s\S]*?(?:```|$)/g,""),e=e.replace(/```tool_result[^\n]*\n[\s\S]*?```/g,""),e=e.replace(/\n{2,}/g,`
|
|
1066
|
-
`).trim(),e.replace(/\s+/g,"").length||(e=""),e=e.replace(/```(\w*)\n?([\s\S]*?)```/g,(t,n,r)=>{const{path:s,rest:o}=
|
|
1070
|
+
`).trim(),e.replace(/\s+/g,"").length||(e=""),e=e.replace(/```(\w*)\n?([\s\S]*?)```/g,(t,n,r)=>{const{path:s,rest:o}=Rz(r),a=`aiblk_${++_z}`;hs.set(a,{code:o,path:s,lang:n||""});const l=/^(markdown|md)$/i.test(n||""),O=l?Zz(o):o.split(`
|
|
1067
1071
|
`).map(f=>f.startsWith("+")?`<span class="ai-diff-add">${Q(f)}</span>`:f.startsWith("-")?`<span class="ai-diff-del">${Q(f)}</span>`:Q(f)).join(`
|
|
1068
1072
|
`),c=s?Q(s):"active file",d=!s&&!B;return s?`<div class="ai-codeblock ai-codeblock-collapsed" data-block-id="${a}" data-auto-apply="1">
|
|
1069
1073
|
<div class="ai-codeblock-bar">
|
|
@@ -1087,7 +1091,7 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
|
|
|
1087
1091
|
</span>
|
|
1088
1092
|
</div>
|
|
1089
1093
|
<pre${l?' class="ai-codeblock-md"':""}>${l?O:`<code>${O}</code>`}</pre>
|
|
1090
|
-
</div>`}),e=e.replace(/`([^`]+)`/g,'<code style="background:rgba(0,0,0,0.3);padding:0.1rem 0.3rem;border-radius:0.2rem">$1</code>'),e=e.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>"),e=e.replace(/\n/g,"<br>"),e=e.replace(/(<div class="ai-codeblock"[\s\S]*?<\/div>)(<br>)+/g,"$1"),e=e.replace(/(<br>)+(<div class="ai-codeblock")/g,"$2"),e}async function
|
|
1094
|
+
</div>`}),e=e.replace(/`([^`]+)`/g,'<code style="background:rgba(0,0,0,0.3);padding:0.1rem 0.3rem;border-radius:0.2rem">$1</code>'),e=e.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>"),e=e.replace(/\n/g,"<br>"),e=e.replace(/(<div class="ai-codeblock"[\s\S]*?<\/div>)(<br>)+/g,"$1"),e=e.replace(/(<br>)+(<div class="ai-codeblock")/g,"$2"),e}async function zz(i){const e=hs.get(i);if(e)try{await navigator.clipboard.writeText(e.code),$i(i,"Copied")}catch{$i(i,"Copy failed",!0)}}function qz(i){const e=hs.get(i);if(!e)return;const t=ee.find(n=>n.path===B);if(!(t!=null&&t.view)){$i(i,"Open a file first",!0);return}t.view.dispatch(t.view.state.replaceSelection(e.code)),t.dirty=!0,Wi(),$i(i,"Inserted")}async function Vz(i){try{await Ue(".")}catch{}if(!i)return;const e=ee.find(t=>t.path===i);if(e)try{const t=await L(`/file?path=${encodeURIComponent(i)}`),n=(t==null?void 0:t.content)??"";e.view&&e.view.dispatch({changes:{from:0,to:e.view.state.doc.length,insert:n}}),e.content=n,e.dirty=!1,Wi(),pi(`Reloaded ${i} after tool call`)}catch{}}function Cz(i){const e=document.querySelector(`.ai-codeblock[data-block-id="${i}"]`);if(!e)return;const t=e.querySelector("pre");t&&(t.style.display=t.style.display==="none"?"":"none")}function bd(i,e,t){const n=document.querySelector(`.ai-codeblock[data-block-id="${i}"] [data-status]`);n&&(n.textContent=e,t&&(n.style.color=t))}async function Yz(i){const e=hs.get(i);if(!e)return;const t=e.path||B;if(!t){bd(i,"✗","var(--danger,#f38ba8)"),$i(i,"No target file",!0);return}try{await L("/file/save","POST",{path:t,content:e.code}),bd(i,"✓","var(--success,#a6e3a1)"),$i(i,"Applied");const n=ee.find(r=>r.path===t);n!=null&&n.view?(n.view.dispatch({changes:{from:0,to:n.view.state.doc.length,insert:e.code}}),n.content=e.code,n.dirty=!1,Wi(),pi(`Applied AI suggestion to ${t}`)):await ui(t),await Ue(".")}catch(n){bd(i,"✗","var(--danger,#f38ba8)"),$i(i,`Apply failed: ${(n==null?void 0:n.message)||n}`,!0)}}async function Wz(i){const e=hs.get(i);if(!e)return;const t=e.path||"src/new-file",n=prompt("Save to path (relative to project root):",t);if(n)try{await L("/file/save","POST",{path:n,content:e.code}),$i(i,`Saved → ${n}`),await Ue("."),await ui(n)}catch(r){$i(i,`Save failed: ${(r==null?void 0:r.message)||r}`,!0)}}function $i(i,e,t=!1){const n=document.querySelector(`.ai-codeblock[data-block-id="${i}"] .ai-codeblock-lang`);if(!n)return;const r=n.textContent;n.textContent=e,n.style.color=t?"var(--danger, #f38ba8)":"var(--success, #a6e3a1)",setTimeout(()=>{n.textContent=r,n.style.color=""},1500)}let wa=null;function Ez(i,e,t){pn();const n=document.createElement("div");n.className="editor-ctx-menu",n.style.cssText=`position:fixed;left:${i.clientX}px;top:${i.clientY}px;z-index:200`,t?n.innerHTML=`
|
|
1091
1095
|
<div class="ctx-item" onclick="window.__editorNewFile('${Q(e)}')">📄 New File <span class="ctx-shortcut">Ctrl+N</span></div>
|
|
1092
1096
|
<div class="ctx-item" onclick="window.__editorNewFolder('${Q(e)}')">📁 New Folder <span class="ctx-shortcut">Ctrl+Shift+N</span></div>
|
|
1093
1097
|
<div class="ctx-sep"></div>
|
|
@@ -1100,19 +1104,19 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
|
|
|
1100
1104
|
<div class="ctx-item" onclick="window.__editorDuplicate('${Q(e)}')">📋 Duplicate</div>
|
|
1101
1105
|
<div class="ctx-sep"></div>
|
|
1102
1106
|
<div class="ctx-item ctx-danger" onclick="window.__editorDelete('${Q(e)}',false)">🗑️ Delete <span class="ctx-shortcut">Del</span></div>
|
|
1103
|
-
`,document.body.appendChild(n),wa=n,setTimeout(()=>{document.addEventListener("click",pn,{once:!0})},0)}function pn(){wa&&(wa.remove(),wa=null)}async function FQ(i){pn();const e=prompt("New file name:");if(!e)return;const t=i==="."?e:`${i}/${e}`;try{await L("/file/save","POST",{path:t,content:""}),await Ue(i),ui(t)}catch(n){alert("Failed: "+n.message)}}async function HQ(i){pn();const e=prompt("New folder name:");if(!e)return;const t=i==="."?e:`${i}/${e}`;try{await L("/file/save","POST",{path:`${t}/.gitkeep`,content:""}),fi.add(t),await Ue(i)}catch(n){alert("Failed: "+n.message)}}async function KQ(i,e){pn();const t=i.split("/"),n=t.pop()||"",r=t.join("/")||".",s=prompt("Rename to:",n);if(!s||s===n)return;const o=r==="."?s:`${r}/${s}`;try{await L("/file/rename","POST",{from:i,to:o});const a=ee.findIndex(l=>l.path===i);a>=0&&(ee[a].path=o,B===i&&(B=o),Wi()),await Ue(r)}catch(a){alert("Rename failed: "+a.message)}}async function JQ(i,e){if(pn(),!!confirm(`Delete ${e?"folder":"file"} "${i}"?`))try{await L("/file/delete","POST",{path:i,is_dir:e}),ee.findIndex(s=>s.path===i)>=0&&fn(i);const r=i.split("/").slice(0,-1).join("/")||".";await Ue(r)}catch(n){alert("Delete failed: "+n.message)}}async function
|
|
1107
|
+
`,document.body.appendChild(n),wa=n,setTimeout(()=>{document.addEventListener("click",pn,{once:!0})},0)}function pn(){wa&&(wa.remove(),wa=null)}async function FQ(i){pn();const e=prompt("New file name:");if(!e)return;const t=i==="."?e:`${i}/${e}`;try{await L("/file/save","POST",{path:t,content:""}),await Ue(i),ui(t)}catch(n){alert("Failed: "+n.message)}}async function HQ(i){pn();const e=prompt("New folder name:");if(!e)return;const t=i==="."?e:`${i}/${e}`;try{await L("/file/save","POST",{path:`${t}/.gitkeep`,content:""}),fi.add(t),await Ue(i)}catch(n){alert("Failed: "+n.message)}}async function KQ(i,e){pn();const t=i.split("/"),n=t.pop()||"",r=t.join("/")||".",s=prompt("Rename to:",n);if(!s||s===n)return;const o=r==="."?s:`${r}/${s}`;try{await L("/file/rename","POST",{from:i,to:o});const a=ee.findIndex(l=>l.path===i);a>=0&&(ee[a].path=o,B===i&&(B=o),Wi()),await Ue(r)}catch(a){alert("Rename failed: "+a.message)}}async function JQ(i,e){if(pn(),!!confirm(`Delete ${e?"folder":"file"} "${i}"?`))try{await L("/file/delete","POST",{path:i,is_dir:e}),ee.findIndex(s=>s.path===i)>=0&&fn(i);const r=i.split("/").slice(0,-1).join("/")||".";await Ue(r)}catch(n){alert("Delete failed: "+n.message)}}async function Az(i){pn();const e=i.split("/"),t=e.pop()||"",n=t.includes(".")?"."+t.split(".").pop():"",s=`${n?t.slice(0,-n.length):t}-copy${n}`,o=e.join("/")||".",a=o==="."?s:`${o}/${s}`;try{const l=await L(`/file?path=${encodeURIComponent(i)}`);await L("/file/save","POST",{path:a,content:l.content}),await Ue(o),ui(a)}catch(l){alert("Duplicate failed: "+l.message)}}function Uz(){const i=document.getElementById("editor-menu-dropdown");if(!i)return;const e=i.style.display==="none";i.style.display=e?"block":"none",e&&setTimeout(()=>{const t=n=>{var r;(r=n.target)!=null&&r.closest(".editor-menu-wrapper")||(i.style.display="none"),document.removeEventListener("click",t)};document.addEventListener("click",t)},0)}async function ey(){var s;const i=document.getElementById("deps-search-input"),e=(s=i==null?void 0:i.value)==null?void 0:s.trim();if(!e)return;const t=document.getElementById("deps-search-results");t&&(t.innerHTML='<div class="text-sm text-muted" style="padding:8px;text-align:center">Searching...</div>');const n=(B==null?void 0:B.split("/").pop())||"",r=ad[n];if(r)try{const o=new AbortController,a=setTimeout(()=>o.abort(),1e4),l=await fetch(`/__dev/api/deps/search?q=${encodeURIComponent(e)}®istry=${r.registry}`,{signal:o.signal});clearTimeout(a);const c=(await l.json()).packages||[];if(!t)return;if(c.length===0){t.innerHTML='<div class="text-sm text-muted" style="padding:8px;text-align:center">No packages found</div>';return}t.innerHTML=c.map(d=>`<div class="deps-item">
|
|
1104
1108
|
<div class="deps-item-name">${Q(d.name)}</div>
|
|
1105
1109
|
<div class="deps-item-desc">${Q(d.description||"")}</div>
|
|
1106
1110
|
<div class="deps-item-meta">
|
|
1107
1111
|
<span>${Q(d.version||"")}</span>
|
|
1108
1112
|
<button class="btn btn-sm" style="font-size:0.6rem;padding:2px 8px;color:var(--success);border-color:var(--success)" onclick="window.__depsInstall('${Q(d.name)}','${Q(d.version||"")}')">+ Install</button>
|
|
1109
1113
|
</div>
|
|
1110
|
-
</div>`).join("")}catch(o){t&&(t.innerHTML=`<div class="text-sm" style="padding:8px;color:var(--danger)">${Q(o.message||"Search failed")}</div>`)}}async function
|
|
1114
|
+
</div>`).join("")}catch(o){t&&(t.innerHTML=`<div class="text-sm" style="padding:8px;color:var(--danger)">${Q(o.message||"Search failed")}</div>`)}}async function jz(i,e){const t=(B==null?void 0:B.split("/").pop())||"",n=ad[t];if(!n)return;const r=document.getElementById("deps-search-results");r&&(r.innerHTML=`<div class="text-sm text-muted" style="padding:8px;text-align:center">Installing ${Q(i)}...</div>`);const s=document.getElementById("deps-dev-toggle"),o=!!(s!=null&&s.checked);try{const a=await L("/deps/install","POST",{name:i,version:e,registry:n.registry,file:B,dev:o});if(r&&(r.innerHTML=`<div class="text-sm" style="padding:8px;color:var(--success)">✔ ${Q(a.message||`Installed ${i}`)}</div>`),B){const l=await L(`/file?path=${encodeURIComponent(B)}`),O=ee.find(c=>c.path===B);O&&l.content&&(O.content=l.content,O.dirty=!1,ld(),RQ(n))}}catch(a){r&&(r.innerHTML=`<div class="text-sm" style="padding:8px;color:var(--danger)">✗ ${Q(a.message||"Install failed")}</div>`)}}window.__depsSearch=ey,window.__depsInstall=jz;async function Mz(i){const e=prompt(`Name for the new ${i}:`);if(!e)return;const t=document.getElementById("scaffold-output");t&&(t.style.display="block",t.textContent=`Generating ${i} "${e}"...`);try{const n=await L("/scaffold/run","POST",{kind:i,name:e}),r=n.path||Dz(n.output);t&&(t.innerHTML=`<span style="color:var(--success)">✔</span> ${Q(`Created ${i}: ${e}`)}`,n.output&&(t.innerHTML+=`
|
|
1111
1115
|
<span style="opacity:0.7">${Q(String(n.output).trim())}</span>`),r&&(t.innerHTML+=`
|
|
1112
|
-
<span style="color:var(--info);cursor:pointer;text-decoration:underline" onclick="window.__editorOpenFile('${Q(r)}')">${Q(r)}</span>`)),Ue("."),r&&setTimeout(()=>ui(r),500)}catch(n){t&&(t.innerHTML=`<span style="color:var(--danger)">✗</span> ${Q(n.message||"Failed")}`)}}const
|
|
1113
|
-
${String(e.output).trim().slice(-600)}`:""}`:e.output||e.message||`${i} complete`}function
|
|
1116
|
+
<span style="color:var(--info);cursor:pointer;text-decoration:underline" onclick="window.__editorOpenFile('${Q(r)}')">${Q(r)}</span>`)),Ue("."),r&&setTimeout(()=>ui(r),500)}catch(n){t&&(t.innerHTML=`<span style="color:var(--danger)">✗</span> ${Q(n.message||"Failed")}`)}}const Lz={migrate:"/migrate",test:"/test",seed:"/seed/run"};async function Iz(i){const e=document.getElementById("scaffold-output"),t=Lz[i];if(!t){e&&(e.style.display="block",e.innerHTML=`<span style="color:var(--danger)">✗</span> Unknown command: ${Q(i)}`);return}e&&(e.style.display="block",e.textContent=`Running ${i}...`);try{const n=await L(t,"POST",{});e&&(e.innerHTML=`<span style="color:var(--success)">✔</span> ${Q(Gz(i,n))}`),(i==="migrate"||i==="seed")&&Ue(".")}catch(n){e&&(e.innerHTML=`<span style="color:var(--danger)">✗</span> ${Q(n.message||"Failed")}`)}}function Gz(i,e){var t,n,r;if(!e||typeof e!="object")return`${i} complete`;if(i==="migrate"){const s=((t=e.applied)==null?void 0:t.length)??0,o=((n=e.skipped)==null?void 0:n.length)??0,a=((r=e.failed)==null?void 0:r.length)??0;return`Migrate: ${s} applied, ${o} skipped${a?`, ${a} failed`:""}`}if(i==="seed"){const s=e.seeded??0,o=e.failed??0;return`Seed: ${s} rows${o?`, ${o} failed`:""}`}return i==="test"?`Tests ${e.ok!==!1&&e.code===0?"passed":"failed"}${e.output?`
|
|
1117
|
+
${String(e.output).trim().slice(-600)}`:""}`:e.output||e.message||`${i} complete`}function Dz(i){if(!i)return;const e=String(i).match(/((?:src|migrations|routes|models)\/[\w./-]+)/);return e?e[1]:void 0}window.__scaffold=Mz,window.__scaffoldRun=Iz;const Sd="tina4.editor.sidebar-width",wd="tina4.editor.right-panel-width";function Bz(){const i=localStorage.getItem(Sd),e=localStorage.getItem(wd),t=document.getElementById("editor-sidebar"),n=document.getElementById("editor-right-panel");t&&i&&(t.style.width=i+"px"),n&&e&&(n.style.width=e+"px")}function Nz(){const i=document.querySelector(".editor-layout"),e=document.getElementById("editor-sidebar"),t=document.getElementById("editor-right-panel"),n=document.getElementById("editor-splitter-left"),r=document.getElementById("editor-splitter-right");!i||!e||!t||!n||!r||(Bz(),ty(n,s=>{const o=parseFloat(getComputedStyle(e).width);return a=>{const l=Math.max(160,Math.min(600,o+a));return e.style.width=l+"px",l}},s=>localStorage.setItem(Sd,String(Math.round(s)))),ty(r,()=>{const s=parseFloat(getComputedStyle(t).width);return o=>{const a=Math.max(200,Math.min(800,s-o));return t.style.width=a+"px",a}},s=>localStorage.setItem(wd,String(Math.round(s)))))}function ty(i,e,t){i.addEventListener("mousedown",n=>{n.preventDefault(),i.classList.add("dragging"),document.body.style.userSelect="none",document.body.style.cursor="col-resize";const r=n.clientX,s=e(r);let o=0;const a=O=>{o=s(O.clientX-r)},l=()=>{document.removeEventListener("mousemove",a),document.removeEventListener("mouseup",l),i.classList.remove("dragging"),document.body.style.userSelect="",document.body.style.cursor="",o&&t(o)};document.addEventListener("mousemove",a),document.addEventListener("mouseup",l)}),i.addEventListener("dblclick",()=>{const n=document.getElementById("editor-sidebar"),r=document.getElementById("editor-right-panel");i.id==="editor-splitter-left"&&n&&(n.style.width="",localStorage.removeItem(Sd)),i.id==="editor-splitter-right"&&r&&(r.style.width="",localStorage.removeItem(wd))})}function Fz(i,e){document.querySelectorAll(".tab-ctx-menu").forEach(h=>h.remove());const t=ee.findIndex(h=>h.path===e),n=t>0,r=t>=0&&t<ee.length-1,s=ee.length>1,o=(h,f,p)=>`<div class="tab-ctx-item ${p?"":"disabled"}"${p?` data-action="${f}"`:""}>
|
|
1114
1118
|
<span>${h}</span>
|
|
1115
|
-
</div>`,a=document.createElement("div");a.className="tab-ctx-menu",a.innerHTML=o("Close","close",!0)+o("Close Others","close-others",s)+'<div class="tab-ctx-sep"></div>'+o("Close to the Left","close-left",n)+o("Close to the Right","close-right",r)+'<div class="tab-ctx-sep"></div>'+o("Close All","close-all",ee.length>0),a.addEventListener("click",h=>{const f=h.target.closest("[data-action]");if(!f)return;switch(f.dataset.action){case"close":fn(e);break;case"close-others":
|
|
1119
|
+
</div>`,a=document.createElement("div");a.className="tab-ctx-menu",a.innerHTML=o("Close","close",!0)+o("Close Others","close-others",s)+'<div class="tab-ctx-sep"></div>'+o("Close to the Left","close-left",n)+o("Close to the Right","close-right",r)+'<div class="tab-ctx-sep"></div>'+o("Close All","close-all",ee.length>0),a.addEventListener("click",h=>{const f=h.target.closest("[data-action]");if(!f)return;switch(f.dataset.action){case"close":fn(e);break;case"close-others":Hz(e);break;case"close-left":Kz(e);break;case"close-right":Jz(e);break;case"close-all":e2();break}a.remove()}),document.body.appendChild(a);const l=Math.min(i.clientX,window.innerWidth-190),O=Math.min(i.clientY,window.innerHeight-a.offsetHeight-10);a.style.left=l+"px",a.style.top=O+"px";const c=h=>{h&&a.contains(h.target)||(a.remove(),document.removeEventListener("mousedown",c,!0),document.removeEventListener("keydown",d,!0),document.removeEventListener("scroll",c,!0))},d=h=>{h.key==="Escape"&&c()};setTimeout(()=>{document.addEventListener("mousedown",c,!0),document.addEventListener("keydown",d,!0),document.addEventListener("scroll",c,!0)},0)}function Hz(i){ee.filter(t=>t.path!==i).map(t=>t.path).forEach(t=>fn(t))}function Kz(i){const e=ee.findIndex(n=>n.path===i);if(e<1)return;ee.slice(0,e).map(n=>n.path).forEach(n=>fn(n))}function Jz(i){const e=ee.findIndex(n=>n.path===i);if(e<0||e>=ee.length-1)return;ee.slice(e+1).map(n=>n.path).forEach(n=>fn(n))}function e2(){ee.map(e=>e.path).forEach(e=>fn(e))}window.__editorToggleMenu=Uz,window.__editorCtxMenu=Ez,window.__editorNewFile=FQ,window.__editorNewFolder=HQ,window.__editorRename=KQ,window.__editorDelete=JQ,window.__editorDuplicate=Az,window.__editorToggleDir=CZ,window.__editorOpenFile=ui,window.__editorSwitchFile=ls,window.__editorCloseFile=fn,window.__editorTabCtxMenu=Fz,window.__editorPopOut=EZ,window.__editorToggleAI=jZ,window.__editorPlanSwitch=NZ,window.__editorPlanOpen=FZ,window.__editorPlanCreate=HZ,window.__editorPlanRun=LZ,window.__editorPlanStop=IZ,window.__editorThoughtDismiss=BZ,window.__aiBlockCopy=zz,window.__aiBlockInsert=qz,window.__aiBlockApply=Yz,window.__aiBlockSaveAs=Wz,window.__aiBlockToggle=Cz,document.addEventListener("keydown",i=>{(i.ctrlKey||i.metaKey)&&i.key==="s"&&B&&(i.preventDefault(),qQ())}),document.addEventListener("keydown",i=>{const e=i.target;(e==null?void 0:e.id)==="deps-search-input"&&i.key==="Enter"&&(i.preventDefault(),ey())}),document.addEventListener("keydown",i=>{var t,n,r;if(!(!B&&!document.querySelector(".editor-layout")||((t=i.target)==null?void 0:t.tagName)==="INPUT"||((n=i.target)==null?void 0:n.tagName)==="TEXTAREA")){if((i.ctrlKey||i.metaKey)&&i.key==="n"&&!i.shiftKey){i.preventDefault();const s=B&&B.split("/").slice(0,-1).join("/")||".";FQ(s)}if((i.ctrlKey||i.metaKey)&&i.key==="N"&&i.shiftKey){i.preventDefault();const s=B&&B.split("/").slice(0,-1).join("/")||".";HQ(s)}i.key==="F2"&&B&&(i.preventDefault(),KQ(B)),i.key==="Delete"&&B&&!((r=i.target)!=null&&r.closest(".cm-editor"))&&(i.preventDefault(),JQ(B,!1))}}),(function(){const e=()=>{document.querySelectorAll(".tina4-fb-btn, .tina4-fb-modal").forEach(t=>t.remove()),window.__tina4FeedbackLoaded=!0};e(),requestAnimationFrame(e),setTimeout(e,500),setTimeout(e,2e3)})();const iy=document.createElement("style");iy.textContent=ly,document.head.appendChild(iy);const Pa=oy();ay(Pa);let ny=[{id:"editor",label:"Code With Me",render:RZ},...[{id:"routes",label:"Routes",render:Oy},{id:"database",label:"Database",render:cy},{id:"graphql",label:"GraphQL",render:Ry},{id:"queue",label:"Queue",render:Vy},{id:"errors",label:"Errors",render:yy},{id:"metrics",label:"Metrics",render:Ty},{id:"system",label:"System",render:Py}]],Pd="editor";function t2(){const i=document.getElementById("app");if(!i)return;i.innerHTML=`
|
|
1116
1120
|
<div class="dev-admin">
|
|
1117
1121
|
<div class="dev-header">
|
|
1118
1122
|
<h1><span>Tina4</span> Dev Admin</h1>
|
|
@@ -1124,4 +1128,4 @@ ${String(e.output).trim().slice(-600)}`:""}`:e.output||e.message||`${i} complete
|
|
|
1124
1128
|
<div class="dev-tabs" id="tab-bar"></div>
|
|
1125
1129
|
<div class="dev-content" id="tab-content"></div>
|
|
1126
1130
|
</div>
|
|
1127
|
-
`;const e=document.getElementById("tab-bar");e.innerHTML=ny.map(t=>`<button class="dev-tab ${t.id===Pd?"active":""}" data-tab="${t.id}" onclick="window.__switchTab('${t.id}')">${t.label}</button>`).join(""),ry(Pd)}function ry(i){Pd=i;const e=document.querySelector(".dev-header"),t=document.getElementById("tab-bar"),n=document.querySelector(".dev-admin"),r=i==="editor";e&&(e.style.display=r?"none":""),t&&(t.style.display=r?"none":""),n&&n.classList.toggle("fullscreen-editor",r),document.querySelectorAll(".dev-tab").forEach(l=>{l.classList.toggle("active",l.dataset.tab===i)});const s=document.getElementById("tab-content");if(!s)return;const o=document.createElement("div");o.className="dev-panel active",s.innerHTML="",s.appendChild(o);const a=ny.find(l=>l.id===i);a&&a.render(o)}function
|
|
1131
|
+
`;const e=document.getElementById("tab-bar");e.innerHTML=ny.map(t=>`<button class="dev-tab ${t.id===Pd?"active":""}" data-tab="${t.id}" onclick="window.__switchTab('${t.id}')">${t.label}</button>`).join(""),ry(Pd)}function ry(i){Pd=i;const e=document.querySelector(".dev-header"),t=document.getElementById("tab-bar"),n=document.querySelector(".dev-admin"),r=i==="editor";e&&(e.style.display=r?"none":""),t&&(t.style.display=r?"none":""),n&&n.classList.toggle("fullscreen-editor",r),document.querySelectorAll(".dev-tab").forEach(l=>{l.classList.toggle("active",l.dataset.tab===i)});const s=document.getElementById("tab-content");if(!s)return;const o=document.createElement("div");o.className="dev-panel active",s.innerHTML="",s.appendChild(o);const a=ny.find(l=>l.id===i);a&&a.render(o)}function i2(){if(window.parent!==window)try{const i=window.parent.document.getElementById("tina4-dev-panel");i&&i.remove()}catch{document.body.style.display="none"}}window.__closeDevAdmin=i2,window.__switchTab=ry,t2(),L("/system").then(i=>{const e=document.getElementById("version-label"),t=i.version||(typeof i.framework=="object"?i.framework.version:null)||(typeof i.framework=="string"?i.framework:null);e&&t&&(e.innerHTML=`${Pa.name} • v${Q(t)}`)}).catch(()=>{const i=document.getElementById("version-label");i&&(i.innerHTML=`${Pa.name}`)})})();
|
package/packages/core/src/ai.ts
CHANGED
|
@@ -116,6 +116,12 @@ function skillsRef(): string {
|
|
|
116
116
|
* The child prints a JSON array of the URLs it fetched successfully; any fetch
|
|
117
117
|
* failure is skipped, never fatal.
|
|
118
118
|
*
|
|
119
|
+
* MEASURED (2026-08-13): a real GitHub raw-content fetch occasionally drops a
|
|
120
|
+
* request under load (transient DNS/TLS hiccup, not a missing file — every
|
|
121
|
+
* URL here resolves fine on its own) while its siblings in the same batch
|
|
122
|
+
* succeed. One retry pass over just the stragglers, still inside the same
|
|
123
|
+
* child process, fixes that for real installer users too, not only the test.
|
|
124
|
+
*
|
|
119
125
|
* @param jobs one entry per unique URL, with every file path it should land in
|
|
120
126
|
* @returns the set of URLs that were fetched and written to disk
|
|
121
127
|
*/
|
|
@@ -125,27 +131,37 @@ function downloadSkillsSync(jobs: { url: string; dests: string[] }[]): Set<strin
|
|
|
125
131
|
const jobs = JSON.parse(process.argv[1]);
|
|
126
132
|
const fs = require("node:fs");
|
|
127
133
|
const path = require("node:path");
|
|
134
|
+
async function fetchOne(job) {
|
|
135
|
+
const resp = await fetch(job.url, { signal: AbortSignal.timeout(15000) });
|
|
136
|
+
if (!resp.ok) throw new Error("HTTP " + resp.status);
|
|
137
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
138
|
+
for (const dest of job.dests) {
|
|
139
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
140
|
+
fs.writeFileSync(dest, buf);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
128
143
|
(async () => {
|
|
129
144
|
const ok = [];
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
145
|
+
let pending = jobs;
|
|
146
|
+
for (let attempt = 0; attempt < 2 && pending.length > 0; attempt++) {
|
|
147
|
+
const failed = [];
|
|
148
|
+
await Promise.all(pending.map(async (job) => {
|
|
149
|
+
try {
|
|
150
|
+
await fetchOne(job);
|
|
151
|
+
ok.push(job.url);
|
|
152
|
+
} catch {
|
|
153
|
+
failed.push(job);
|
|
138
154
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
155
|
+
}));
|
|
156
|
+
pending = failed;
|
|
157
|
+
}
|
|
142
158
|
process.stdout.write(JSON.stringify(ok));
|
|
143
159
|
})();
|
|
144
160
|
`;
|
|
145
161
|
try {
|
|
146
162
|
const out = execFileSync(process.execPath, ["-e", child, JSON.stringify(jobs)], {
|
|
147
163
|
encoding: "utf-8",
|
|
148
|
-
timeout:
|
|
164
|
+
timeout: 50000,
|
|
149
165
|
maxBuffer: 8 * 1024 * 1024,
|
|
150
166
|
});
|
|
151
167
|
return new Set<string>(JSON.parse(out || "[]"));
|
package/packages/core/src/api.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { randomBytes } from "node:crypto";
|
|
|
20
20
|
import { promises as fsp, createWriteStream } from "node:fs";
|
|
21
21
|
import { basename } from "node:path";
|
|
22
22
|
import { pipeline } from "node:stream/promises";
|
|
23
|
+
import { TINA4_VERSION } from "./version.js";
|
|
23
24
|
|
|
24
25
|
export interface ApiResult {
|
|
25
26
|
http_code: number | null;
|
|
@@ -617,10 +618,17 @@ export class Api {
|
|
|
617
618
|
}
|
|
618
619
|
|
|
619
620
|
/**
|
|
620
|
-
* Build the request headers (auth + cookie jar +
|
|
621
|
-
* body to a Buffer. Shared by every verb,
|
|
622
|
-
*
|
|
623
|
-
* path would.
|
|
621
|
+
* Build the request headers (default User-Agent + auth + cookie jar +
|
|
622
|
+
* extras) and serialize the body to a Buffer. Shared by every verb,
|
|
623
|
+
* upload, and download so the wire shape is identical and the transport
|
|
624
|
+
* seam sees exactly what the network path would.
|
|
625
|
+
*
|
|
626
|
+
* VERSION-DEC-03 (feature 130): every outbound request carries a default
|
|
627
|
+
* `Tina4/<version>` User-Agent. `this.headers` is spread AFTER the
|
|
628
|
+
* default, and `extraHeaders` after that, so a caller-supplied
|
|
629
|
+
* `User-Agent` (via the constructor's `headers` option, `addHeaders()`,
|
|
630
|
+
* or a per-call `extraHeaders`) always wins -- this is a default, never a
|
|
631
|
+
* clobber.
|
|
624
632
|
*/
|
|
625
633
|
private buildRequest(
|
|
626
634
|
method: string,
|
|
@@ -628,7 +636,7 @@ export class Api {
|
|
|
628
636
|
body: unknown,
|
|
629
637
|
extraHeaders?: Record<string, string>,
|
|
630
638
|
): { headers: Record<string, string>; data: Buffer | undefined } {
|
|
631
|
-
const headers: Record<string, string> = { ...this.headers };
|
|
639
|
+
const headers: Record<string, string> = { "User-Agent": `Tina4/${TINA4_VERSION}`, ...this.headers };
|
|
632
640
|
if (this.authHeader) {
|
|
633
641
|
headers["Authorization"] = this.authHeader;
|
|
634
642
|
}
|
|
@@ -50,12 +50,15 @@ const _tasks: BackgroundTask[] = [];
|
|
|
50
50
|
*
|
|
51
51
|
* @param callback Function to call (sync or async, no arguments).
|
|
52
52
|
* @param intervalSeconds Seconds between invocations (default: 1).
|
|
53
|
-
* @returns A handle whose `stop()` clears just this one task
|
|
53
|
+
* @returns A handle whose `stop()` clears just this one task and returns whether
|
|
54
|
+
* it removed a live task (true) or was already stopped (false). This is
|
|
55
|
+
* the ONE background surface — a stop-handle plus a count — shared with
|
|
56
|
+
* Python/PHP/Ruby (`handle.stop()` -> bool, `backgroundTaskCount()`).
|
|
54
57
|
*/
|
|
55
58
|
export function background(
|
|
56
59
|
callback: () => unknown | Promise<unknown>,
|
|
57
60
|
intervalSeconds = 1,
|
|
58
|
-
): { stop: () =>
|
|
61
|
+
): { stop: () => boolean } {
|
|
59
62
|
if (typeof callback !== "function") {
|
|
60
63
|
throw new TypeError("background(callback, interval): callback must be a function");
|
|
61
64
|
}
|
|
@@ -99,11 +102,14 @@ export function background(
|
|
|
99
102
|
_tasks.push(task);
|
|
100
103
|
|
|
101
104
|
return {
|
|
102
|
-
stop: () => {
|
|
105
|
+
stop: (): boolean => {
|
|
106
|
+
// Idempotent: true the first time (a live task was removed), false after.
|
|
107
|
+
if (task.stopped) return false;
|
|
103
108
|
task.stopped = true;
|
|
104
109
|
clearTimeout(task.timer);
|
|
105
110
|
const idx = _tasks.indexOf(task);
|
|
106
111
|
if (idx !== -1) _tasks.splice(idx, 1);
|
|
112
|
+
return true;
|
|
107
113
|
},
|
|
108
114
|
};
|
|
109
115
|
}
|