slapify 0.0.14 โ†’ 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +342 -258
  2. package/dist/ai/interpreter.d.ts +13 -0
  3. package/dist/ai/interpreter.js +1 -293
  4. package/dist/browser/agent.js +1 -485
  5. package/dist/cli.js +1 -1315
  6. package/dist/config/loader.js +1 -305
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +1 -260
  9. package/dist/parser/flow.js +1 -117
  10. package/dist/perf/audit.d.ts +215 -0
  11. package/dist/perf/audit.js +1 -0
  12. package/dist/report/generator.d.ts +1 -0
  13. package/dist/report/generator.js +1 -549
  14. package/dist/runner/index.d.ts +14 -1
  15. package/dist/runner/index.js +1 -584
  16. package/dist/task/index.d.ts +5 -0
  17. package/dist/task/index.js +1 -0
  18. package/dist/task/report.d.ts +9 -0
  19. package/dist/task/report.js +1 -0
  20. package/dist/task/runner.d.ts +3 -0
  21. package/dist/task/runner.js +1 -0
  22. package/dist/task/session.d.ts +18 -0
  23. package/dist/task/session.js +1 -0
  24. package/dist/task/tools.d.ts +253 -0
  25. package/dist/task/tools.js +1 -0
  26. package/dist/task/types.d.ts +153 -0
  27. package/dist/task/types.js +1 -0
  28. package/dist/types.d.ts +2 -0
  29. package/dist/types.js +1 -2
  30. package/package.json +25 -15
  31. package/dist/ai/interpreter.d.ts.map +0 -1
  32. package/dist/ai/interpreter.js.map +0 -1
  33. package/dist/browser/agent.d.ts.map +0 -1
  34. package/dist/browser/agent.js.map +0 -1
  35. package/dist/cli.d.ts.map +0 -1
  36. package/dist/cli.js.map +0 -1
  37. package/dist/config/loader.d.ts.map +0 -1
  38. package/dist/config/loader.js.map +0 -1
  39. package/dist/index.d.ts.map +0 -1
  40. package/dist/index.js.map +0 -1
  41. package/dist/parser/flow.d.ts.map +0 -1
  42. package/dist/parser/flow.js.map +0 -1
  43. package/dist/report/generator.d.ts.map +0 -1
  44. package/dist/report/generator.js.map +0 -1
  45. package/dist/runner/index.d.ts.map +0 -1
  46. package/dist/runner/index.js.map +0 -1
  47. package/dist/types.d.ts.map +0 -1
  48. package/dist/types.js.map +0 -1
@@ -0,0 +1 @@
1
+ import t from"fs";import e from"path";function n(t){return{navigate:"๐ŸŒ",get_page_state:"๐Ÿ“„",click:"๐Ÿ–ฑ๏ธ",type:"โŒจ๏ธ",press:"โŒจ๏ธ",scroll:"โ†•๏ธ",wait:"โณ",screenshot:"๐Ÿ“ธ",reload:"๐Ÿ”„",go_back:"โฌ…๏ธ",list_credential_profiles:"๐Ÿ”‘",inject_credentials:"๐Ÿ’‰",fill_login_form:"๐Ÿ”",save_credentials:"๐Ÿ’พ",remember:"๐Ÿง ",recall:"๐Ÿ”",list_memories:"๐Ÿ“š",schedule:"โฐ",sleep_until:"๐Ÿ˜ด",done:"โœ…",fetch_url:"โšก",status_update:"๐Ÿ“ข",ask_user:"๐Ÿ™‹"}[t]||"๐Ÿ”ง"}function o(t){return t>=1048576?`${(t/1048576).toFixed(1)} MB`:t>=1024?`${(t/1024).toFixed(0)} KB`:`${t} B`}function r(t){const e=t.scores??t.lighthouse??null,n=e?["performance","accessibility","bestPractices","seo"].map(t=>{const n=e[t]??0,o="bestPractices"===t?"Best Practices":t.charAt(0).toUpperCase()+t.slice(1),r=function(t){return t>=90?"#22c55e":t>=50?"#f59e0b":"#ef4444"}(n),s=2*Math.PI*28;return`<div style="text-align:center;min-width:100px">\n <svg width="72" height="72" viewBox="0 0 72 72">\n <circle cx="36" cy="36" r="28" fill="none" stroke="#1e293b" stroke-width="8"/>\n <circle cx="36" cy="36" r="28" fill="none" stroke="${r}" stroke-width="8"\n stroke-dasharray="${(n/100*s).toFixed(1)} ${s.toFixed(1)}"\n stroke-linecap="round" transform="rotate(-90 36 36)"/>\n <text x="36" y="41" text-anchor="middle" fill="${r}" font-size="16" font-weight="700">${n}</text>\n </svg>\n <div style="font-size:0.72rem;color:#94a3b8;margin-top:4px">${o}</div>\n <div style="font-size:0.68rem;color:${r}">${function(t){return t>=90?"Good":t>=50?"Needs Improvement":"Poor"}(n)}</div>\n </div>`}).join(""):"",r=Object.entries(t.vitals).filter(([,t])=>null!=t).map(([t,e])=>{const n="cls"===t?e.toFixed(4):`${e}ms`,o=function(t,e){const n={fcp:[1800,3e3],lcp:[2500,4e3],cls:[.1,.25],ttfb:[800,1800],tbt:[200,600]}[t];return n?e<=n[0]?"#22c55e":e<=n[1]?"#f59e0b":"#ef4444":"#94a3b8"}(t,e);return`<tr>\n <td style="color:#94a3b8;padding:6px 12px;font-size:0.8rem">${{fcp:"First Contentful Paint",lcp:"Largest Contentful Paint",cls:"Cumulative Layout Shift",ttfb:"Time to First Byte",domContentLoaded:"DOM Content Loaded",loadComplete:"Load Complete"}[t]||t}</td>\n <td style="color:${o};padding:6px 12px;font-size:0.8rem;font-weight:600">${n}</td>\n </tr>`}).join(""),s=e?[["FCP",e.fcp,"ms"],["LCP",e.lcp,"ms"],["CLS",e.cls?.toFixed(4),""],["TBT",e.tbt,"ms"],["Speed Index",e.speedIndex,"ms"],["TTI",e.tti,"ms"]].filter(([,t])=>null!=t).map(([t,e,n])=>`<div style="background:#1e293b;border-radius:8px;padding:12px 16px;min-width:120px">\n <div style="font-size:0.72rem;color:#64748b">${t}</div>\n <div style="font-size:1.1rem;font-weight:700;color:#e2e8f0;margin-top:2px">${e}${n}</div>\n </div>`).join(""):"",l=t.react?.version,d=l?.startsWith("(")?l.slice(1,-1):l,i=d?` <span style="color:#64748b;font-weight:400;font-size:0.8rem">${a(d)}</span>`:"",c=t.react?.detected&&(t.react.issues?.length??0)>0?`<table style="width:100%;border-collapse:collapse;margin-top:8px">\n <thead><tr>\n <th style="text-align:left;color:#64748b;font-size:0.72rem;text-transform:uppercase;padding:6px 12px">Component</th>\n <th style="text-align:left;color:#64748b;font-size:0.72rem;text-transform:uppercase;padding:6px 12px">Renders</th>\n ${null!=t.react.issues[0]?.avgMs?'<th style="text-align:left;color:#64748b;font-size:0.72rem;text-transform:uppercase;padding:6px 12px">Avg Time</th>':""}\n </tr></thead>\n <tbody>\n ${t.react.issues.map(t=>`<tr>\n <td style="color:#fcd34d;padding:6px 12px;font-size:0.8rem;font-family:monospace">${a(t.component)}</td>\n <td style="color:#f87171;padding:6px 12px;font-size:0.8rem;font-weight:600">${t.renderCount}</td>\n ${null!=t.avgMs?`<td style="color:#94a3b8;padding:6px 12px;font-size:0.8rem">${t.avgMs}ms</td>`:""}\n </tr>`).join("")}\n </tbody>\n </table>`:"",p=t.react?.interactionTests??[],m=p.length>0?`<div style="margin-top:16px">\n <h4 style="font-size:0.8rem;font-weight:600;color:#94a3b8;margin-bottom:8px;text-transform:uppercase;letter-spacing:0.05em">Interaction Tests</h4>\n <table style="width:100%;border-collapse:collapse">\n <thead><tr>\n <th style="text-align:left;color:#64748b;font-size:0.72rem;text-transform:uppercase;padding:6px 12px">Action</th>\n <th style="text-align:left;color:#64748b;font-size:0.72rem;text-transform:uppercase;padding:6px 12px">DOM Changes</th>\n <th style="text-align:left;color:#64748b;font-size:0.72rem;text-transform:uppercase;padding:6px 12px">Status</th>\n </tr></thead>\n <tbody>\n ${p.map(t=>`<tr>\n <td style="color:#e2e8f0;padding:6px 12px;font-size:0.8rem">${a(t.action)}</td>\n <td style="color:${t.flagged?"#f87171":"#86efac"};padding:6px 12px;font-size:0.8rem;font-weight:600">${t.mutations}</td>\n <td style="padding:6px 12px;font-size:0.8rem">${t.flagged?'<span style="color:#f87171">โš  High activity</span>':'<span style="color:#86efac">โœ“ Normal</span>'}</td>\n </tr>`).join("")}\n </tbody>\n </table>\n </div>`:"",f=t.react?.detected?`<div style="margin-top:24px">\n <h3 style="font-size:0.9rem;font-weight:600;color:#e2e8f0;margin-bottom:12px">โš›๏ธ Framework Analysis${i}</h3>\n ${c||'<p style="color:#22c55e;font-size:0.85rem">โœ… No passive re-render issues detected</p>'}\n ${m}\n </div>`:!1===t.react?.detected?'<p style="color:#64748b;font-size:0.85rem;margin-top:16px">โ„น๏ธ No component framework detected on this page</p>':"";return`\n <div style="background:#0f172a;border:1px solid #1e293b;border-radius:12px;padding:24px;margin-bottom:32px">\n <h2 style="font-size:1.1rem;font-weight:600;color:#e2e8f0;margin-bottom:20px">โšก Performance\n <span style="font-size:0.75rem;font-weight:400;color:#64748b;margin-left:8px">${a(t.url)}</span>\n </h2>\n\n ${n?`<div style="display:flex;gap:24px;flex-wrap:wrap;margin-bottom:24px">${n}</div>`:""}\n\n ${s?`<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:24px">${s}</div>`:""}\n\n ${r?`<div style="margin-bottom:16px">\n <h3 style="font-size:0.85rem;font-weight:600;color:#94a3b8;margin-bottom:8px">Real User Metrics</h3>\n <table style="border-collapse:collapse"><tbody>${r}</tbody></table>\n </div>`:""}\n\n ${f}\n\n ${function(t){if(!t)return"";const e='style="text-align:left;color:#64748b;font-size:0.72rem;text-transform:uppercase;padding:5px 10px"',n=(t="#e2e8f0")=>`style="color:${t};padding:5px 10px;font-size:0.8rem"`,r=(t,e,n="#94a3b8")=>`<div style="background:#1e293b;border-radius:8px;padding:10px 14px;min-width:100px">\n <div style="font-size:0.68rem;color:#64748b">${t}</div>\n <div style="font-size:1rem;font-weight:700;color:${n};margin-top:2px">${e}</div>\n </div>`,s=[r("Requests",String(t.totalRequests)),r("Total Size",o(t.totalBytes),t.totalBytes>2e6?"#f87171":"#e2e8f0"),r("JavaScript",o(t.jsBytes),t.jsBytes>5e5?"#f59e0b":"#e2e8f0"),r("CSS",o(t.cssBytes)),r("Images",o(t.imageBytes)),r("Long Tasks",String(t.longTasks.length),t.longTasks.length>3?"#f59e0b":"#e2e8f0"),r("Blocking JS",`${t.totalBlockingMs}ms`,t.totalBlockingMs>300?"#f87171":t.totalBlockingMs>100?"#f59e0b":"#22c55e"),...null!=t.memoryMB?[r("JS Heap",`${t.memoryMB} MB`)]:[]].join(""),l=t.heaviestResources.filter(t=>t.size>0).map(t=>`<tr>\n <td ${n()}>${a(t.url.split("/").slice(-3).join("/"))}</td>\n <td ${n("#94a3b8")}>${t.type}</td>\n <td ${n(t.size>5e5?"#f87171":t.size>1e5?"#f59e0b":"#86efac")}>${o(t.size)}</td>\n <td ${n("#64748b")}>${t.duration}ms</td>\n ${t.renderBlocking?'<td style="color:#f87171;font-size:0.75rem;padding:5px 10px">โš  blocking</td>':"<td></td>"}\n </tr>`).join(""),d=l?`<div style="margin-top:16px">\n <h4 style="font-size:0.8rem;font-weight:600;color:#94a3b8;margin-bottom:8px;text-transform:uppercase;letter-spacing:0.05em">Heaviest Resources</h4>\n <table style="width:100%;border-collapse:collapse">\n <thead><tr>\n <th ${e}>URL</th><th ${e}>Type</th><th ${e}>Size</th><th ${e}>Load</th><th ${e}></th>\n </tr></thead>\n <tbody>${l}</tbody>\n </table>\n </div>`:"",i=t.apiCalls.slice(0,20).map(t=>{const e=t.duration>=500,o=t.failed?"#f87171":"#86efac";return`<tr>\n <td ${n("#7dd3fc")}>${a(t.method)}</td>\n <td ${n()}>${a(t.url.length>80?"โ€ฆ"+t.url.slice(-80):t.url)}</td>\n <td ${n(o)}>${t.status||"err"}</td>\n <td ${n(e?"#f87171":t.duration>200?"#f59e0b":"#86efac")}>${t.duration}ms${e?" โš ":""}</td>\n </tr>`}).join(""),c=i?`<div style="margin-top:16px">\n <h4 style="font-size:0.8rem;font-weight:600;color:#94a3b8;margin-bottom:8px;text-transform:uppercase;letter-spacing:0.05em">\n API Calls <span style="color:#64748b;font-weight:400;font-size:0.72rem">${t.failedApiCalls.length>0?`ยท ${t.failedApiCalls.length} failed`:""}${t.slowApiCalls.length>0?` ยท ${t.slowApiCalls.length} slow`:""}</span>\n </h4>\n <table style="width:100%;border-collapse:collapse">\n <thead><tr>\n <th ${e}>Method</th><th ${e}>URL</th><th ${e}>Status</th><th ${e}>Time</th>\n </tr></thead>\n <tbody>${i}</tbody>\n </table>\n </div>`:"",p=t.longTasks.sort((t,e)=>e.duration-t.duration).slice(0,8).map(t=>`<tr>\n <td ${n(t.duration>200?"#f87171":"#f59e0b")}>${t.duration}ms</td>\n <td ${n("#64748b")}>at ${t.startTime}ms</td>\n </tr>`).join("");return`\n <div style="border-top:1px solid #1e293b;margin-top:24px;padding-top:20px">\n <h3 style="font-size:0.9rem;font-weight:600;color:#e2e8f0;margin-bottom:14px">๐ŸŒ Network & Runtime</h3>\n <div style="display:flex;gap:10px;flex-wrap:wrap;margin-bottom:4px">${s}</div>\n ${d}\n ${c}\n ${p&&t.longTasks.length>0?`<div style="margin-top:16px">\n <h4 style="font-size:0.8rem;font-weight:600;color:#94a3b8;margin-bottom:8px;text-transform:uppercase;letter-spacing:0.05em">\n Long Tasks <span style="color:#64748b;font-weight:400;font-size:0.72rem">(JS blocking main thread &gt;50ms)</span>\n </h4>\n <table style="width:100%;border-collapse:collapse">\n <thead><tr><th ${e}>Duration</th><th ${e}>When</th></tr></thead>\n <tbody>${p}</tbody>\n </table>\n </div>`:""}\n </div>`}(t.network)}\n\n ${t.lighthouseReportPath?`<p style="margin-top:16px;font-size:0.78rem;color:#64748b">Full report: <a href="${a(t.lighthouseReportPath)}" style="color:#7dd3fc">${a(t.lighthouseReportPath)}</a></p>`:""}\n </div>`}function s(t){const e=t.perfAudits??(t.perfAudit?[t.perfAudit]:[]);if(0===e.length)return"";if(1===e.length)return r(e[0]);return`\n <div>\n <div style="display:flex;flex-wrap:wrap;border-bottom:1px solid #1e293b;margin-bottom:20px;overflow-x:auto">\n ${e.map((t,e)=>`\n <button\n id="slp-tab-${e}"\n onclick="slpSwitch(${e})"\n title="${a(t.url)}"\n style="\n cursor:pointer;background:none;border:none;outline:none;\n padding:9px 18px;font-size:0.82rem;font-weight:500;white-space:nowrap;\n border-bottom:2px solid ${0===e?"#7c3aed":"transparent"};\n color:${0===e?"#e2e8f0":"#64748b"};\n transition:color .15s,border-color .15s;\n "\n >${a((t=>{try{return new URL(t.url).pathname||"/"}catch{return t.url}})(t))}</button>`).join("")}\n </div>\n ${e.map((t,e)=>`<div id="slp-panel-${e}" style="display:${0===e?"block":"none"}">${r(t)}</div>`).join("")}\n </div>\n ${`\n <script>\n function slpSwitch(idx) {\n var n = ${e.length};\n for (var i = 0; i < n; i++) {\n var p = document.getElementById('slp-panel-' + i);\n var t = document.getElementById('slp-tab-' + i);\n var active = i === idx;\n if (p) p.style.display = active ? 'block' : 'none';\n if (t) {\n t.style.borderBottomColor = active ? '#7c3aed' : 'transparent';\n t.style.color = active ? '#e2e8f0' : '#64748b';\n }\n }\n }\n <\/script>`}`}function a(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function l(t){const e=Object.entries(t);return 0===e.length?"":e.map(([t,e])=>{const n="string"==typeof e?e:JSON.stringify(e);return`<span class="arg-key">${a(t)}</span><span class="arg-eq">=</span><span class="arg-val">${a(n.slice(0,120))}${n.length>120?"โ€ฆ":""}</span>`}).join(" &nbsp; ")}export function generateTaskReportHtml(t){const{session:e,events:o}=t,r=new Date(e.updatedAt).getTime()-new Date(e.createdAt).getTime(),d=r<6e4?`${Math.round(r/1e3)}s`:`${Math.floor(r/6e4)}m ${Math.round(r%6e4/1e3)}s`,i=o.filter(t=>["tool_call","tool_error","memory_update","scheduled","sleeping_until","session_end","llm_response"].includes(t.type)).map(t=>{const e=new Date(t.ts).toLocaleTimeString();if("tool_call"===t.type){const o=n(t.toolName);return`\n <tr class="${"done"===t.toolName?"row-done":"row-tool"}">\n <td class="cell-time">${e}</td>\n <td class="cell-icon">${o}</td>\n <td class="cell-tool"><span class="tag-tool">${a(t.toolName)}</span></td>\n <td class="cell-args">${l(t.args)}</td>\n <td class="cell-result result-ok">${function(t){const e="string"==typeof t?t:JSON.stringify(t);return a(e.slice(0,300))+(e.length>300?"โ€ฆ":"")}(t.result)}</td>\n </tr>`}if("tool_error"===t.type){return`\n <tr class="row-error">\n <td class="cell-time">${e}</td>\n <td class="cell-icon">${n(t.toolName)}</td>\n <td class="cell-tool"><span class="tag-tool tag-error">${a(t.toolName)}</span></td>\n <td class="cell-args">${l(t.args)}</td>\n <td class="cell-result result-error">${a(t.error)}</td>\n </tr>`}if("memory_update"===t.type)return`\n <tr class="row-memory">\n <td class="cell-time">${e}</td>\n <td class="cell-icon">๐Ÿง </td>\n <td class="cell-tool"><span class="tag-memory">remember</span></td>\n <td class="cell-args"><span class="arg-key">${a(t.key)}</span> = <span class="arg-val">${a(t.value.slice(0,120))}</span></td>\n <td class="cell-result result-ok">stored</td>\n </tr>`;if("scheduled"===t.type)return`\n <tr class="row-scheduled">\n <td class="cell-time">${e}</td>\n <td class="cell-icon">โฐ</td>\n <td class="cell-tool"><span class="tag-scheduled">schedule</span></td>\n <td class="cell-args"><span class="arg-key">cron</span>=<span class="arg-val">${a(t.cron)}</span></td>\n <td class="cell-result result-ok">${a(t.task)}</td>\n </tr>`;if("sleeping_until"===t.type)return`\n <tr class="row-sleeping">\n <td class="cell-time">${e}</td>\n <td class="cell-icon">๐Ÿ˜ด</td>\n <td class="cell-tool"><span class="tag-sleeping">sleep</span></td>\n <td class="cell-args"></td>\n <td class="cell-result result-ok">until ${a(new Date(t.until).toLocaleString())}</td>\n </tr>`;if("llm_response"===t.type&&t.text){return`\n <tr class="row-think">\n <td class="cell-time">${e}</td>\n <td class="cell-icon">๐Ÿ’ฌ</td>\n <td class="cell-tool"><span class="tag-think">thought</span></td>\n <td class="cell-args" colspan="2"><span class="markdown-inline" data-md="${a(t.text.slice(0,300))}${t.text.length>300?"โ€ฆ":""}"></span></td>\n </tr>`}return""}).join("\n"),c=Object.entries(e.memory).map(([t,e])=>`\n <tr>\n <td class="mem-key">${a(t)}</td>\n <td class="mem-val">${a(e)}</td>\n </tr>`).join("\n"),p=e.scheduledJobs.map(t=>`\n <tr>\n <td>${a(t.cron)}</td>\n <td>${a(t.taskDescription)}</td>\n <td>${t.lastRun?new Date(t.lastRun).toLocaleString():"โ€”"}</td>\n </tr>`).join("\n"),m=e.finalSummary?`<div class="summary-box markdown-body" data-md="${a(e.finalSummary)}"></div>`:"";return`<!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="UTF-8"/>\n <meta name="viewport" content="width=device-width, initial-scale=1.0"/>\n <title>Task Report โ€” ${a(e.goal.slice(0,60))}</title>\n <script src="https://cdn.tailwindcss.com"><\/script>\n <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"><\/script>\n <style>\n body { font-family: 'Inter', system-ui, sans-serif; background: #0f172a; color: #e2e8f0; }\n .summary-box { background: #1e293b; border-left: 4px solid #22c55e; padding: 1rem 1.5rem; border-radius: 0.5rem; font-size: 0.95rem; line-height: 1.7; }\n /* Markdown prose styles inside summary and thought cells */\n .markdown-body h1,.markdown-body h2,.markdown-body h3 { font-weight: 700; margin: 0.75em 0 0.35em; color: #f1f5f9; }\n .markdown-body h1 { font-size: 1.25rem; }\n .markdown-body h2 { font-size: 1.05rem; }\n .markdown-body h3 { font-size: 0.95rem; }\n .markdown-body p { margin: 0.4em 0; }\n .markdown-body ul,.markdown-body ol { padding-left: 1.4em; margin: 0.4em 0; }\n .markdown-body li { margin: 0.15em 0; }\n .markdown-body strong { color: #f8fafc; font-weight: 600; }\n .markdown-body em { color: #cbd5e1; }\n .markdown-body code { background: #0f172a; color: #7dd3fc; padding: 1px 5px; border-radius: 4px; font-size: 0.85em; font-family: monospace; }\n .markdown-body pre { background: #0f172a; border-radius: 6px; padding: 0.75rem 1rem; overflow-x: auto; margin: 0.5em 0; }\n .markdown-body pre code { background: none; padding: 0; }\n .markdown-body hr { border-color: #334155; margin: 0.75em 0; }\n .markdown-body blockquote { border-left: 3px solid #475569; padding-left: 0.75rem; color: #94a3b8; margin: 0.4em 0; }\n .markdown-body table { border-collapse: collapse; width: 100%; margin: 0.5em 0; font-size: 0.85rem; }\n .markdown-body th { background: #1e293b; color: #94a3b8; padding: 6px 12px; text-align: left; font-weight: 600; border: 1px solid #334155; }\n .markdown-body td { padding: 5px 12px; border: 1px solid #1e293b; color: #e2e8f0; }\n .markdown-body tr:nth-child(even) td { background: #0f1b2d; }\n /* Inline thought text (smaller, muted) */\n .markdown-inline p { margin: 0; display: inline; }\n .markdown-inline { color: #94a3b8; font-size: 0.82rem; }\n table { border-collapse: collapse; width: 100%; }\n th { background: #1e293b; color: #94a3b8; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; padding: 0.6rem 0.75rem; text-align: left; }\n td { padding: 0.45rem 0.75rem; font-size: 0.82rem; border-bottom: 1px solid #1e293b; vertical-align: top; }\n tr:hover td { background: #1e293b55; }\n .cell-time { color: #64748b; font-family: monospace; white-space: nowrap; width: 70px; }\n .cell-icon { font-size: 1rem; text-align: center; width: 30px; }\n .cell-tool { white-space: nowrap; }\n .cell-args { color: #94a3b8; font-size: 0.78rem; }\n .arg-key { color: #7dd3fc; }\n .arg-eq { color: #64748b; }\n .arg-val { color: #fcd34d; }\n .tag-tool { background: #1e40af33; color: #93c5fd; padding: 1px 7px; border-radius: 999px; font-size: 0.73rem; }\n .tag-error { background: #7f1d1d44; color: #fca5a5; }\n .tag-memory { background: #4c1d9544; color: #c4b5fd; padding: 1px 7px; border-radius: 999px; font-size: 0.73rem; }\n .tag-scheduled { background: #164e6344; color: #67e8f9; padding: 1px 7px; border-radius: 999px; font-size: 0.73rem; }\n .tag-sleeping { background: #3b0764; color: #d8b4fe; padding: 1px 7px; border-radius: 999px; font-size: 0.73rem; }\n .tag-think { background: #422006; color: #fde68a; padding: 1px 7px; border-radius: 999px; font-size: 0.73rem; }\n .result-ok { color: #86efac; }\n .result-error { color: #fca5a5; }\n .row-done td { background: #14532d22; }\n .row-error td { background: #7f1d1d22; }\n .row-memory td { background: #1a0533; }\n .row-scheduled td { background: #0c4a6e22; }\n .row-sleeping td { background: #150a33; }\n .row-think td { background: #1c1a00; }\n .mem-key { color: #7dd3fc; font-family: monospace; width: 220px; }\n .mem-val { color: #fde68a; font-family: monospace; white-space: pre-wrap; word-break: break-all; }\n .stat-card { background: #1e293b; border-radius: 0.75rem; padding: 1.25rem 1.5rem; }\n .stat-num { font-size: 2rem; font-weight: 700; line-height: 1; }\n .stat-label { font-size: 0.75rem; color: #64748b; margin-top: 0.25rem; }\n </style>\n</head>\n<body class="min-h-screen">\n <div class="max-w-6xl mx-auto px-4 py-10">\n <div class="mb-8">\n <div class="flex items-center gap-3 mb-1">\n <span class="text-2xl">๐Ÿค–</span>\n <h1 class="text-2xl font-bold text-white">Task Report</h1>\n <span style="color:${function(t){switch(t){case"completed":return"#22c55e";case"failed":return"#ef4444";case"scheduled":return"#3b82f6";case"sleeping":return"#a78bfa";default:return"#f59e0b"}}(e.status)}" class="ml-2 text-lg">${function(t){switch(t){case"completed":return"โœ…";case"failed":return"โŒ";case"scheduled":return"โฐ";case"sleeping":return"๐Ÿ˜ด";default:return"โŸณ"}}(e.status)} ${e.status}</span>\n </div>\n <p class="text-slate-400 text-sm mt-1 font-mono">${a(e.id)}</p>\n <p class="text-slate-200 text-base mt-3 font-medium">"${a(e.goal)}"</p>\n </div>\n\n <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">\n <div class="stat-card">\n <div class="stat-num text-blue-400">${e.iteration}</div>\n <div class="stat-label">Iterations</div>\n </div>\n <div class="stat-card">\n <div class="stat-num text-green-400">${d}</div>\n <div class="stat-label">Duration</div>\n </div>\n <div class="stat-card">\n <div class="stat-num text-purple-400">${Object.keys(e.memory).length}</div>\n <div class="stat-label">Memory Items</div>\n </div>\n <div class="stat-card">\n <div class="stat-num text-cyan-400">${e.scheduledJobs.length}</div>\n <div class="stat-label">Scheduled Jobs</div>\n </div>\n </div>\n\n ${m?`\n <div class="mb-8">\n <h2 class="text-lg font-semibold text-white mb-3">๐Ÿ“‹ Summary</h2>\n ${m}\n </div>`:""}\n\n ${s(e)}\n\n ${c?`\n <div class="mb-8">\n <h2 class="text-lg font-semibold text-white mb-3">๐Ÿง  Memory at Completion</h2>\n <div class="rounded-lg overflow-hidden border border-slate-700">\n <table>\n <thead><tr><th>Key</th><th>Value</th></tr></thead>\n <tbody>${c}</tbody>\n </table>\n </div>\n </div>`:""}\n\n ${p?`\n <div class="mb-8">\n <h2 class="text-lg font-semibold text-white mb-3">โฐ Scheduled Jobs</h2>\n <div class="rounded-lg overflow-hidden border border-slate-700">\n <table>\n <thead><tr><th>Cron</th><th>Task</th><th>Last Run</th></tr></thead>\n <tbody>${p}</tbody>\n </table>\n </div>\n </div>`:""}\n\n <div class="mb-8">\n <h2 class="text-lg font-semibold text-white mb-3">๐Ÿ“œ Action Timeline</h2>\n <div class="rounded-lg overflow-hidden border border-slate-700">\n <table>\n <thead>\n <tr><th>Time</th><th></th><th>Action</th><th>Arguments</th><th>Result</th></tr>\n </thead>\n <tbody>${i}</tbody>\n </table>\n </div>\n </div>\n\n <p class="text-center text-slate-600 text-xs mt-8">\n Generated by Slapify Task Agent &bull; ${new Date(t.generatedAt).toLocaleString()}\n &bull; Session: ${a(e.id)}\n </p>\n </div>\n\n <script>\n // Render all markdown blocks once marked.js is loaded\n (function renderMarkdown() {\n if (typeof marked === 'undefined') {\n // Retry after CDN script loads\n window.addEventListener('load', renderMarkdown);\n return;\n }\n marked.setOptions({ breaks: true, gfm: true });\n\n // Summary box โ€” full markdown prose\n document.querySelectorAll('.markdown-body[data-md]').forEach(function(el) {\n el.innerHTML = marked.parse(el.getAttribute('data-md') || '');\n });\n\n // Inline thought previews โ€” strip to plain text then render inline\n document.querySelectorAll('.markdown-inline[data-md]').forEach(function(el) {\n el.innerHTML = marked.parseInline(el.getAttribute('data-md') || '');\n });\n })();\n <\/script>\n</body>\n</html>`}export function saveTaskReport(n,o,r){const s=r||e.join(process.cwd(),"slapify-task-reports");t.existsSync(s)||t.mkdirSync(s,{recursive:!0});const a=e.join(s,`${n.id}.html`),l=generateTaskReportHtml({session:n,events:o,generatedAt:(new Date).toISOString()});return t.writeFileSync(a,l),a}
@@ -0,0 +1,3 @@
1
+ import { TaskRunOptions, TaskSession } from "./types.js";
2
+ export declare function runTask(options: TaskRunOptions): Promise<TaskSession>;
3
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ import e from"fs";import t from"path";import{generateText as s}from"ai";import n from"node-cron";import{createSession as a}from"wreq-js";import{BrowserAgent as o}from"../browser/agent.js";import{loadConfig as r,loadCredentials as i}from"../config/loader.js";import{getModel as l}from"../ai/interpreter.js";import{taskTools as c}from"./tools.js";import{createSession as u,loadSession as h,saveSessionMeta as d,appendEvent as p,updateSessionStatus as m}from"./session.js";const g=200;class f{browser;session;credentials;emit;onHumanInput;credentialsFilePath;isScheduledRun;wreqSession=null;constructor(e,t,s,n,a,o,r=!1){this.browser=e,this.session=t,this.credentials=s,this.emit=n,this.onHumanInput=a,this.credentialsFilePath=o,this.isScheduledRun=r}async getWreqSession(){return this.wreqSession||(this.wreqSession=await a({browser:"chrome_131",os:"macos"})),this.wreqSession}async closeWreqSession(){if(this.wreqSession){try{await this.wreqSession.close()}catch{}this.wreqSession=null}}async execute(s,a){switch(s){case"navigate":{const e=a.url;await this.browser.navigate(e);try{await this.browser.evaluate("(function(){document.querySelectorAll('a[target=\"_blank\"]').forEach(function(a){a.setAttribute('target','_self');});if(!window.__slapifyPatched){window.__slapifyPatched=true;var _orig=window.open;window.open=function(url,name,features){if(url&&String(url).startsWith('http')){window.location.href=url;return window;}return _orig.apply(this,arguments);};}})()")}catch{}return{ok:!0,url:e}}case"get_page_state":{const e=await this.browser.getState();return{url:e.url,title:e.title,snapshot:e.snapshot,refsCount:Object.keys(e.refs).length}}case"click":{const e=a.ref;try{await this.browser.evaluate("(function(){document.querySelectorAll('a[target=\"_blank\"]').forEach(function(a){a.setAttribute('target','_self');});if(!window.__slapifyPatched){window.__slapifyPatched=true;var _orig=window.open;window.open=function(url,name,features){if(url&&String(url).startsWith('http')){window.location.href=url;return window;}return _orig.apply(this,arguments);};}})()")}catch{}return await this.browser.click(e),{ok:!0,clicked:e}}case"type":{const e=a.ref,t=a.text;return a.append?await this.browser.type(e,t):await this.browser.fill(e,t),{ok:!0}}case"press":{const e=a.key;return await this.browser.press(e),{ok:!0}}case"scroll":{const e=a.direction,t=a.amount||300;return await this.browser.scroll(e,t),{ok:!0}}case"wait":{const e=a.seconds;return await this.browser.wait(1e3*e),{ok:!0,waited:`${e}s`}}case"screenshot":return{ok:!0,path:await this.browser.screenshot(),note:"Screenshot captured. Check get_page_state() for interactive elements."};case"reload":return await this.browser.reload(),{ok:!0};case"go_back":return await this.browser.goBack(),{ok:!0};case"list_credential_profiles":return{profiles:Object.entries(this.credentials).map(([e,t])=>({name:e,type:t.type,hasUsername:!(!t.username&&!t.email),hasCookies:!!(t.cookies&&t.cookies.length>0),hasLocalStorage:!!(t.localStorage&&Object.keys(t.localStorage).length>0)}))};case"inject_credentials":{const e=a.profile_name,t=this.credentials[e];return t?"inject"!==t.type?{ok:!1,error:`Profile '${e}' is type '${t.type}', use fill_login_form for login-form profiles`}:(await this.injectProfile(t),await this.browser.wait(300),await this.browser.reload(),{ok:!0,injected:e}):{ok:!1,error:`Profile '${e}' not found`}}case"fill_login_form":{const e=a.profile_name,t=this.credentials[e];return t?"login-form"!==t.type?{ok:!1,error:`Profile '${e}' is type '${t.type}'. Use inject_credentials for inject profiles.`}:{ok:!0,username:t.username||t.email||t.phone||"",password:t.password||"",hint:"Use get_page_state() to find the username/password fields, then type into them and submit the form."}:{ok:!1,error:`Profile '${e}' not found`}}case"solve_captcha":{const e=await this.browser.getState(),t=(e.snapshot,[]),s=[],n=Object.entries(e.refs).filter(([,e])=>{const t=(e.name||e.text||"").toLowerCase();return t.includes("not a robot")||t.includes("i'm not a robot")||t.includes("checkbox")||"checkbox"===e.role});for(const[e]of n)try{await this.browser.click(e),await this.browser.wait(2e3),t.push(`Clicked checkbox ref ${e}`)}catch{s.push(`Failed to click ref ${e}`)}const a=Object.entries(e.refs).filter(([,e])=>{const t=(e.name||e.text||"").toLowerCase();return t.includes("audio")||t.includes("sound")});for(const[e]of a.slice(0,1))try{await this.browser.click(e),await this.browser.wait(1500),t.push(`Clicked audio challenge ref ${e}`)}catch{s.push(`Failed to click audio ref ${e}`)}const o=await this.browser.getState(),r=o.snapshot?.toLowerCase().includes("captcha")||o.snapshot?.toLowerCase().includes("not a robot")||o.url?.includes("sorry");return{attempted:t.length>0,solved:t,failed:s,captchaStillPresent:r,currentUrl:o.url,hint:r?"CAPTCHA still present. Try fetch_url() on a different source for the same data.":"CAPTCHA appears resolved. Call get_page_state() to continue."}}case"fetch_url":{const e=a.url,t=a.headers||{},s=await this.getWreqSession(),n=await s.fetch(e,{headers:{Accept:"application/json, text/html, */*","Accept-Language":"en-US,en;q=0.9",...t}}),o=n.headers.get("content-type")||"",r=await n.text();let i=r;if(o.includes("application/json"))try{i=JSON.parse(r)}catch{i=r}const l="string"==typeof i?i:JSON.stringify(i);return{ok:n.ok,status:n.status,body:l.slice(0,8e3)+(l.length>8e3?"โ€ฆ[truncated]":"")}}case"remember":{const e=a.key,t=a.value;return this.session.memory[e]=t,d(this.session),p(this.session.id,{type:"memory_update",key:e,value:t,ts:(new Date).toISOString()}),{ok:!0,stored:e}}case"recall":{const e=a.key,t=this.session.memory[e];return void 0!==t?{ok:!0,key:e,value:t}:{ok:!1,key:e,error:"Key not found in memory"}}case"list_memories":return{keys:Object.keys(this.session.memory),count:Object.keys(this.session.memory).length};case"status_update":{const e=a.message;return this.emit({type:"status_update",message:e}),{ok:!0}}case"ask_user":{const s=a.question,n=a.hint;this.emit({type:"human_input_needed",question:s,hint:n});const o=await this.onHumanInput(s,n);p(this.session.id,{type:"tool_call",toolName:"ask_user",args:{question:s,hint:n},result:{answer:"[redacted from logs]"},ts:(new Date).toISOString()});if(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z]{2,}\b/i.test(o)&&o.includes(" ")){const s=o.split(/\s+(?:and\s+)?(?:password\s+is\s+|pass(?:word)?[:\s]+)?/i),n=s[0]?.trim(),a=s[1]?.trim();this.emit({type:"human_input_needed",question:"๐Ÿ’พ Save these credentials for future sessions?",hint:"Profile name to save as (or press Enter to skip)"});const r=await this.onHumanInput("๐Ÿ’พ Save these credentials for future sessions?","Enter a profile name (e.g. 'linkedin', 'gmail') or leave blank to skip");if(r&&r.trim()){const s=r.trim().toLowerCase().replace(/\s+/g,"-"),o={type:"login-form",...n&&{username:n},...a&&{password:a}};try{const n=(await import("yaml")).default;let a={profiles:{}};if(e.existsSync(this.credentialsFilePath))try{const t=n.parse(e.readFileSync(this.credentialsFilePath,"utf-8"));t?.profiles&&(a=t)}catch{}a.profiles[s]=o,e.mkdirSync(t.dirname(this.credentialsFilePath),{recursive:!0}),e.writeFileSync(this.credentialsFilePath,n.stringify(a,{indent:2,lineWidth:0})),this.credentials[s]=o,this.emit({type:"credentials_saved",profileName:s,credType:"login-form"})}catch{}}}return{answer:o}}case"save_credentials":{const s=a.profile_name,n=a.type,o=a.capture_from_browser,r={type:n};if("login-form"===n&&(a.username&&(r.username=a.username),a.password&&(r.password=a.password)),"inject"===n&&o)try{const e=await this.browser.getCookies(),t=await this.browser.getLocalStorage(),s=await this.browser.getSessionStorage();e.length>0&&(r.cookies=e.map(e=>({name:e.name,value:e.value})));const n=e=>{if(!e||"object"!=typeof e||Array.isArray(e))return{};const t={};for(const[s,n]of Object.entries(e))t[String(s)]="string"==typeof n?n:JSON.stringify(n);return t},a=n(t),o=n(s);Object.keys(a).length>0&&(r.localStorage=a),Object.keys(o).length>0&&(r.sessionStorage=o)}catch(e){return{ok:!1,error:`Failed to capture browser state: ${e.message}`}}try{const a=(await import("yaml")).default;let o={profiles:{}};if(e.existsSync(this.credentialsFilePath))try{const t=a.parse(e.readFileSync(this.credentialsFilePath,"utf-8"));t?.profiles&&(o=t)}catch{}return o.profiles[s]=r,e.mkdirSync(t.dirname(this.credentialsFilePath),{recursive:!0}),e.writeFileSync(this.credentialsFilePath,a.stringify(o,{indent:2,lineWidth:0})),this.credentials[s]=r,this.emit({type:"credentials_saved",profileName:s,credType:n}),{ok:!0,message:`Saved profile '${s}' (${n}) to credentials.yaml`,cookieCount:r.cookies?.length??0,localStorageKeys:Object.keys(r.localStorage??{}).length}}catch(e){return{ok:!1,error:`Failed to save credentials: ${e.message}`}}}case"perf_audit":{const e=a.url,t=!1!==a.lighthouse,s=!1!==a.react_scan;this.emit({type:"status_update",message:`โšก Auditing ${e}...`});try{const{runPerfAudit:n}=await import("../perf/audit.js"),a=await n(e,this.browser,{lighthouse:t,reactScan:s,settleMs:2e3,navigate:!0});this.session.perfAudits||(this.session.perfAudits=[]),this.session.perfAudits.push(a),this.session.perfAudit=a,d(this.session);const o=a.scores??a.lighthouse,r=a.network,i={url:a.url,vitals:a.vitals,scores:o,react:a.react,network:r?{totalRequests:r.totalRequests,totalKB:Math.round((r.totalBytes||0)/1024),jsKB:Math.round((r.jsBytes||0)/1024),apiCalls:r.apiCalls.length,slowApiCalls:r.slowApiCalls.length,failedApiCalls:r.failedApiCalls.length,longTasks:r.longTasks.length,totalBlockingMs:r.totalBlockingMs,memoryMB:r.memoryMB,slowApis:r.slowApiCalls.slice(0,5).map(e=>({url:e.url.length>80?"โ€ฆ"+e.url.slice(-80):e.url,method:e.method,status:e.status,durationMs:e.duration})),heaviestResources:r.heaviestResources.slice(0,5).map(e=>({url:e.url.split("/").slice(-2).join("/"),type:e.type,sizeKB:Math.round(e.size/1024)}))}:null},l=[`Audit complete for ${e}`];if(a.vitals.fcp&&l.push(`FCP: ${a.vitals.fcp}ms`),a.vitals.lcp&&l.push(`LCP: ${a.vitals.lcp}ms`),null!=a.vitals.cls&&l.push(`CLS: ${a.vitals.cls}`),o&&l.push(`Scores โ€” Perf ${o.performance}/100 ยท A11y ${o.accessibility}/100 ยท SEO ${o.seo}/100`),a.react?.detected){const e=a.react.version?.startsWith("(")?a.react.version.slice(1,-1):a.react.version,t=a.react.interactionTests??[],s=t.filter(e=>e.flagged).length;l.push(`Framework: ${e||"React"} ยท Re-render issues: ${a.react.issues.length}${t.length?` ยท Interaction tests: ${t.length} (${s} flagged)`:""}`)}return r&&l.push(`Network: ${r.totalRequests} requests ยท ${Math.round((r.totalBytes||0)/1024)}KB total ยท JS ${Math.round((r.jsBytes||0)/1024)}KB ยท ${r.apiCalls.length} API calls${r.slowApiCalls.length?` (${r.slowApiCalls.length} slow)`:""}${r.failedApiCalls.length?` (${r.failedApiCalls.length} failed)`:""} ยท ${r.longTasks.length} long tasks (${r.totalBlockingMs}ms)`),this.emit({type:"status_update",message:l.join(" ยท ")}),i}catch(e){return{ok:!1,error:`Performance audit failed: ${e.message}`}}}case"schedule":{const e=a.cron,t=a.task_description;return this.isScheduledRun?{ok:!1,error:"You are already running as a scheduled sub-task. Do NOT call schedule() again โ€” the parent cron is still active. Use status_update() to report findings, then finish. The next check will happen automatically."}:n.validate(e)?(this.session.scheduledJobs.push({id:`job-${Date.now()}`,cron:e,taskDescription:t,createdAt:(new Date).toISOString()}),d(this.session),p(this.session.id,{type:"scheduled",cron:e,task:t,ts:(new Date).toISOString()}),this.emit({type:"scheduled",cron:e,task:t}),{ok:!0,message:`Task scheduled: '${t}' with cron '${e}'. The process will stay alive and re-run at each interval.`}):{ok:!1,error:`Invalid cron expression: ${e}`}}case"sleep_until":{const e=a.until,t=a.reason||"",s=function(e){const t=new Date(e);if(!isNaN(t.getTime())){const e=t.getTime()-Date.now();return Math.max(0,e)}const s=e.toLowerCase().trim(),n=[[/^(\d+(?:\.\d+)?)\s*s(?:ec(?:onds?)?)?$/,1e3],[/^(\d+(?:\.\d+)?)\s*m(?:in(?:utes?)?)?$/,6e4],[/^(\d+(?:\.\d+)?)\s*h(?:ours?)?$/,36e5],[/^(\d+(?:\.\d+)?)\s*d(?:ays?)?$/,864e5]];for(const[e,t]of n){const n=s.match(e);if(n)return Math.round(parseFloat(n[1])*t)}if(s.includes("tomorrow")){const e=new Date,t=new Date(e);t.setDate(t.getDate()+1);const n=s.match(/(\d{1,2})(?::(\d{2}))?\s*(am|pm)?/);if(n){let e=parseInt(n[1]);const s=parseInt(n[2]||"0");"pm"===n[3]&&e<12&&(e+=12),"am"===n[3]&&12===e&&(e=0),t.setHours(e,s,0,0)}return Math.max(0,t.getTime()-Date.now())}return 6e4}(e),n=new Date(Date.now()+s).toISOString();return p(this.session.id,{type:"sleeping_until",until:n,ts:(new Date).toISOString()}),this.emit({type:"sleeping",until:n}),m(this.session,"sleeping"),await new Promise(e=>setTimeout(e,s)),m(this.session,"running"),{ok:!0,sleptUntil:n,reason:t}}case"done":return{ok:!0};default:return{ok:!1,error:`Unknown tool: ${s}`}}}async injectProfile(e){if(e.cookies)for(const t of e.cookies)try{await this.browser.setCookie(t.name,t.value)}catch{}if(e.localStorage)for(const[t,s]of Object.entries(e.localStorage))try{await this.browser.setLocalStorage(t,s)}catch{}if(e.sessionStorage)for(const[t,s]of Object.entries(e.sessionStorage))try{await this.browser.setSessionStorage(t,s)}catch{}}}async function y(e,t,n){const a=e.slice(0,e.length-20),o=e.slice(e.length-20);if(0===a.length)return e;try{const{text:r}=await s({model:t,messages:[{role:"user",content:"Summarize the following agent conversation history into a compact but detailed summary. Include: what was accomplished, current state, important findings stored in memory, any failures and what was tried. This summary will replace the history to save context.\n\n"+JSON.stringify(a,null,2)}]});return p(n,{type:"context_compacted",fromMessages:e.length,toMessages:1+o.length,ts:(new Date).toISOString()}),[{role:"user",content:`[Session history summary]\n${r}`},...o]}catch{return o}}const w=new Set(["get_page_state","screenshot","wait","scroll","recall","list_memories","list_credential_profiles","go_back","reload","fetch_url","solve_captcha","status_update","ask_user","save_credentials"]);class k{recentActions=[];WINDOW=20;THRESHOLD=5;record(e,t){if(w.has(e))return;const s=`${e}:${JSON.stringify(t)}`;this.recentActions.push(s),this.recentActions.length>this.WINDOW&&this.recentActions.shift()}isLooping(){if(this.recentActions.length<this.WINDOW)return!1;const e=new Map;for(const t of this.recentActions)e.set(t,(e.get(t)||0)+1);return[...e.values()].some(e=>e>=this.THRESHOLD)}}export async function runTask(e){const{goal:a,sessionId:w,headed:b,executablePath:v,saveFlow:S,flowOutputDir:C,maxIterations:$=g,onEvent:T,onSessionUpdate:A,isScheduledRun:I=!1,inheritedMemory:N}=e,O=e=>T?.(e),P=r(),x=l(P.llm);let D={};try{D=i().profiles||{}}catch{}const j=new o({headless:!0!==b&&(!1===b||(P.browser?.headless??!0)),timeout:P.browser?.timeout,viewport:P.browser?.viewport,executablePath:v||P.browser?.executablePath});let R,L;if(w){const e=h(w);if(!e)throw new Error(`Session '${w}' not found.`);R=e,R.status="running",d(R);const{rebuildMessages:t}=await import("./session.js");L=t((await import("./session.js")).loadEvents(w)),O({type:"message",text:`Resuming session ${w} (iteration ${R.iteration})`})}else R=u(a),N&&Object.keys(N).length>0&&(Object.assign(R.memory,N),d(R)),L=[{role:"user",content:a}],p(R.id,{type:"session_start",goal:a,ts:(new Date).toISOString()}),O({type:"message",text:`Session ${R.id} started`});A?.(R);let E=t.join(process.cwd(),".slapify","credentials.yaml");try{const{getConfigDir:e}=await import("../config/loader.js"),s=e();s&&(E=t.join(s,"credentials.yaml"))}catch{}const M=e.onHumanInput??(async(e,t)=>{const s=(await import("readline")).createInterface({input:process.stdin,output:process.stdout});return new Promise(n=>{const a=t?`\n ${e}\n (${t})\n > `:`\n ${e}\n > `;s.question(a,e=>{s.close(),n(e.trim())})})}),F=new f(j,R,D,O,M,E,I),U=new k;if(Object.keys(R.memory).length>0){const e=Object.entries(R.memory).map(([e,t])=>`- ${e}: ${t}`).join("\n");let t;if(I){const s=R.memory.thread_url||R.memory.conversation_url;t=`[SCHEDULED CHECK-IN โ€” you are a recurring monitoring run]\nThis is NOT the first run. A parent cron job spawned you. Do NOT call schedule() again.\nYour job: check for new activity, respond if needed, call done() when finished.\n\nContext from parent session:\n${e}`+(s?`\nIMPORTANT: Navigate directly to ${s} โ€” do NOT start a new login flow if you are already on LinkedIn.`:"")}else t=`[Memory from previous session]\n${e}`;L.unshift({role:"user",content:t})}else I&&L.unshift({role:"user",content:"[SCHEDULED CHECK-IN] You are a recurring monitoring run. Do NOT call schedule() again. Check for new activity, respond if needed, then call done()."});let H=!1,q="";try{for(;!H&&R.iteration<$;){R.iteration++,d(R),A?.(R),p(R.id,{type:"iteration_start",iteration:R.iteration,ts:(new Date).toISOString()}),L.length>60&&(O({type:"message",text:"Compacting context..."}),L=await y(L,x,R.id)),O({type:"thinking"});const e=await s({model:x,system:'You are Slapify Task Agent โ€” a fully autonomous web agent. You decide the best approach for any task yourself.\n\n## Tools\n- **fetch_url(url)** โ€” Direct HTTP GET, bypasses browser. No CAPTCHA. Instant. Use for APIs and data.\n- **navigate(url)** + **get_page_state()** โ€” Browser navigation. get_page_state() returns all visible text and interactive refs.\n- **click(ref)**, **type(ref, text)**, **press(key)**, **scroll(direction)**, **wait(seconds)** โ€” Browser interaction.\n- **list_credential_profiles()**, **inject_credentials(profile)**, **fill_login_form(profile)** โ€” Authentication.\n- **remember(key, value)**, **recall(key)**, **list_memories()** โ€” Persistent memory.\n- **schedule(cron, task)**, **sleep_until(datetime)** โ€” Time-based control.\n- **perf_audit(url)** โ€” Full performance audit for a URL. Automatically navigates to the page, then collects: scores (Performance/Accessibility/SEO/Best Practices 0-100), real-user metrics (FCP, LCP, CLS, TTFB), framework detection, re-render analysis with simulated interactions, and network analysis (resource sizes, API calls, long tasks).\n You can call perf_audit multiple times with different URLs to compare pages โ€” the report will show a side-by-side comparison table automatically.\n Do NOT call navigate() before perf_audit โ€” it handles navigation itself.\n If the user asks to audit multiple pages on a domain (e.g. "check pricing and about on vercel.com"), also audit the root/home page (e.g. https://vercel.com/) unless they explicitly say to skip it.\n When summarising results, use neutral section labels. Never write "Lighthouse", "React Scan", or any vendor tool name. Use: "Performance Scores", "Real User Metrics", "Lab Metrics", "Framework & Re-renders", "Interaction Tests", "Network & Runtime".\n The result includes a "network" field with: totalRequests, totalKB, jsKB, apiCalls count, slowApiCalls (>500ms), failedApiCalls, longTasks count, totalBlockingMs, memoryMB, slowApis list, and heaviestResources list. Include these in your summary.\n- **done(summary)** โ€” Signal task complete with full results.\n\n## How to approach any task\n\n**Step 1 โ€” Plan before acting.** Decide: is this a data lookup, an interactive task, or an authenticated task?\n\n**Data lookup** (prices, news, weather, facts, rates):\n Think: does a free public HTTP API exist for this? Most financial/data topics have open APIs.\n Try fetch_url() on a likely API endpoint first โ€” it returns data in <1s with no CAPTCHA or JS rendering issues.\n If you find useful JSON, parse it and call done() immediately.\n If no API works, navigate to a site and read get_page_state() โ€” it contains all visible text.\n\n**Interactive task** (filling forms, clicking buttons, posting content):\n Use the browser. Navigate โ†’ get_page_state() โ†’ interact using ref IDs from the snapshot.\n\n**Authenticated task** (anything requiring login):\n 1. Check memory for a saved thread_url or page_url โ€” navigate directly there first.\n 2. Call get_page_state() โ€” if the URL is the target site (not a login page), you are already logged in. Proceed.\n 3. Only if you see a login form: call list_credential_profiles() and use the best matching profile.\n This avoids unnecessary re-login on every scheduled check-in.\n\n**Monitoring / ongoing task** โ€” CRITICAL RULE:\n Keywords: "monitor", "keep checking", "wait for reply", "keep me updated", "feel free to engage",\n "notify when", "let me know when", "keep watching", "ongoing", "until X happens"\n\n These tasks NEVER call done() on their own. The user stops them with Ctrl+C.\n Correct flow (FIRST RUN โ€” initial session):\n 1. Perform the first action (send message, check price, etc.)\n 2. IMMEDIATELY call remember() to store key context:\n - remember("thread_url", "<exact URL of the conversation/page>")\n - remember("last_message_sent", "<text of message you sent>")\n - remember("monitoring_target", "<name of person/thing being monitored>")\n 3. Call status_update() to confirm what was done\n 4. Call schedule() with a sensible cron interval (e.g. every 5 min for messages, every hour for prices)\n 5. Do NOT call done() โ€” the process stays alive, re-running at each cron interval\n\n Correct flow (SCHEDULED CHECK-IN โ€” sub-run spawned by cron):\n 1. Check memory for thread_url โ€” navigate DIRECTLY there (do NOT start from homepage)\n 2. Check if you are already logged in by reading get_page_state(). If the URL is the target page, you ARE logged in.\n 3. Only log in if the page is a login form. Use list_credential_profiles() to find the right profile.\n 4. After navigating to the thread, read get_page_state() to find the latest messages\n 5. Compare with last_message_sent in memory โ€” look for NEW messages from the other person\n 6. If there is a new message: respond naturally, then update remember("last_message_sent", ...) with your reply\n 7. If no new message: call status_update("No new reply from <person> yet. Checking again later.")\n 8. Call done() โ€” the cron will re-run automatically. Do NOT call schedule() again.\n\n Example โ€” "send a message and monitor for reply":\n FIRST RUN:\n โ†’ Send message\n โ†’ remember("thread_url", "https://www.linkedin.com/messaging/thread/...")\n โ†’ remember("last_message_sent", "Hello Payal! ๐Ÿ‘‹ How are you doing?")\n โ†’ status_update("โœ‰๏ธ Message sent. Monitoring for reply every 5 minutes.")\n โ†’ schedule("*/5 * * * *", "Check LinkedIn messages from Payal Sahu and respond if she replied")\n [do NOT call done()]\n\n SCHEDULED CHECK-IN:\n โ†’ recall("thread_url") โ†’ navigate directly to that URL\n โ†’ get_page_state() โ†’ find latest message in the thread\n โ†’ if Target replied: type and send a response, remember("last_message_sent", ...)\n โ†’ status_update("โœ… Target replied: \'...\' โ€” responded with \'...\'" OR "No new reply yet.")\n โ†’ done() [cron handles the next run]\n\n**Recurring task** ("every day", "check hourly", "daily at 9am"):\n Execute once, then call schedule() with the cron expression you choose. Don\'t call done().\n\n## Handling obstacles โ€” figure it out\n\n**CAPTCHA in browser:**\n โ†’ First try fetch_url() on the same URL or a different source โ€” direct HTTP never triggers CAPTCHA.\n โ†’ If you must solve it in the browser: call get_page_state() to inspect the CAPTCHA. Look for an iframe or checkbox element. For reCAPTCHA v2, find and click the "I\'m not a robot" checkbox ref. For image CAPTCHAs, look for the audio challenge link.\n โ†’ If one site gives CAPTCHA, try a completely different site with the same data.\n\n**"Just a moment..." / Cloudflare:**\n โ†’ Bot protection. Switch to fetch_url() on that URL, or find a different site entirely.\n\n**Empty page snapshot / "no interactive elements":**\n โ†’ JS-rendered page. Call wait(3) then get_page_state() again.\n โ†’ Still empty? Try fetch_url() on the same URL โ€” the raw HTML often has the data even when browser rendering fails.\n\n**API returns error / bad format:**\n โ†’ Try a different endpoint. Think what other public data sources exist for this topic.\n\n**Stuck after multiple attempts:**\n โ†’ Change strategy completely. If browser isn\'t working, use fetch_url(). If one site fails, try another.\n โ†’ Never repeat the same failing action more than twice.\n -> Make sure to not guess URLs unless you are 100% sure about it, prefer navigating by clicking on available options.\n\n## Batching tool calls โ€” reduce round trips\n\nYou can return **multiple tool calls in a single response** when they don\'t depend on each other\'s output.\nThis is faster โ€” all calls in one response execute in parallel before the next LLM turn.\n\n**Good batching examples:**\n- After a snapshot you already have refs โ†’ batch: type(emailRef, ...) + type(passwordRef, ...) + click(submitRef)\n- Memory + notification โ†’ batch: remember(key, val) + status_update(msg)\n- Click then wait โ†’ batch: click(ref) + wait(2) (wait does not need click result)\n- Multiple remembers โ†’ batch: remember(k1, v1) + remember(k2, v2) + remember(k3, v3)\n\n**Do NOT batch when the second call needs the first call\'s output:**\n- navigate + click โ†’ you need get_page_state() in between to learn the ref\n- get_page_state + click โ†’ click ref comes FROM get_page_state result\n\n**Login form shortcut** โ€” once you have refs from a snapshot, fill the whole form in ONE response:\n type(emailRef, email) + type(passwordRef, password) + click(submitRef)\n Then in the NEXT response: wait(3) + get_page_state() to verify.\n\n## Reading data\n\n- get_page_state() snapshot contains ALL visible text: prices, numbers, paragraphs, labels. Read it carefully before giving up.\n- Do NOT use screenshot() for data extraction โ€” you cannot see images.\n- Always call get_page_state() after every navigate().\n\n## Human in the loop\n\nUse **ask_user(question, hint?)** when you genuinely need information not available elsewhere:\n- A one-time password (OTP) or 2FA code\n- A missing password or PIN that isn\'t in the credential store\n- Clarification about what the user wants when the goal is ambiguous\n- Confirmation before taking a destructive or irreversible action\n\nKeep questions concise. Use the hint field to tell them where to find the answer (e.g. "Check your authenticator app").\n\n**MANDATORY after every successful login:**\n1. Call save_credentials with capture_from_browser: true immediately after you verify the login worked.\n Use a sensible profile_name (e.g. "linkedin", "gmail", "twitter").\n This saves the session cookies so they can be reused next time without logging in again.\n2. Then call status_update("โœ… Logged in as [username]. Session saved as \'[profile_name]\' for future use.")\n\nDo not ask the user whether to save โ€” just save automatically. If the site was already logged in via injected credentials, skip this step.\n\n## Keeping the user informed\n\nUse **status_update(message)** to post visible updates whenever something meaningful happens:\n- When starting a scheduled check: "โฐ Running scheduled gold price check..."\n- When you find data: "๐Ÿ“Š Found gold price: $4,986/oz"\n- When retrying or switching approach: "๐Ÿ”„ Switching to Yahoo Finance..."\n- When sleeping: "๐Ÿ˜ด Waiting 30 minutes before next check. Last price: $4,986/oz"\n- For recurring tasks: post a status_update at the start and end of each run\n\nDo NOT use status_update for every small step โ€” only for things the user would actually want to see.\n\n## Completion rules\n- Use remember() the moment you find important data, before calling done().\n- Call done() with a complete, specific summary including exact data found.\n- Never give up without trying at least 4-5 different approaches.\n- Never ask the user for help โ€” figure it out.\n- **NEVER call done() if the task involves monitoring, waiting for replies, or ongoing engagement.**\n Those tasks end only when the user presses Ctrl+C. Use schedule() instead.\n',messages:L,tools:c}),t=(e.toolCalls||[]).map(e=>({toolCallId:e.toolCallId,toolName:e.toolName,args:e.args}));p(R.id,{type:"llm_response",text:e.text||"",toolCalls:t,ts:(new Date).toISOString()}),e.text&&O({type:"message",text:e.text});const n=[];e.text&&n.push({type:"text",text:e.text});for(const t of e.toolCalls||[])n.push({type:"tool-call",toolCallId:t.toolCallId,toolName:t.toolName,args:t.args});if(n.length>0&&L.push({role:"assistant",content:n}),!e.toolCalls||0===e.toolCalls.length){if("stop"===e.finishReason){q=e.text||"Task complete.",H=!0;break}continue}const o=[];for(const t of e.toolCalls){const e=t.toolName,s=t.args;if("done"===e){if(q=s.summary||"Task complete.",H=!0,s.save_flow||S){const e=await _(R,a,C);R.savedFlowPath=e,O({type:"flow_saved",path:e})}o.push({type:"tool-result",toolCallId:t.toolCallId,toolName:e,result:JSON.stringify({ok:!0})}),p(R.id,{type:"tool_call",toolName:e,args:s,result:{ok:!0},ts:(new Date).toISOString()});break}if(U.record(e,s),U.isLooping()){O({type:"message",text:"Loop detected โ€” changing approach..."}),o.push({type:"tool-result",toolCallId:t.toolCallId,toolName:e,result:JSON.stringify({ok:!1,error:"Loop detected: you have been repeating the same actions. Change your approach or call done()."})});continue}let n;O({type:"tool_start",toolName:e,args:s});try{n=await F.execute(e,s),p(R.id,{type:"tool_call",toolName:e,args:s,result:n,ts:(new Date).toISOString()});const a="string"==typeof n?n:JSON.stringify(n);O({type:"tool_done",toolName:e,result:a.slice(0,200)}),o.push({type:"tool-result",toolCallId:t.toolCallId,toolName:e,result:a})}catch(n){const a=n?.message||String(n);p(R.id,{type:"tool_error",toolName:e,args:s,error:a,ts:(new Date).toISOString()}),O({type:"tool_error",toolName:e,error:a}),o.push({type:"tool-result",toolCallId:t.toolCallId,toolName:e,result:JSON.stringify({ok:!1,error:a})})}}o.length>0&&L.push({role:"tool",content:o})}R.iteration>=$&&!H?(q=`Task hit the maximum iteration limit (${$}) without completing.`,m(R,"failed")):H&&(R.scheduledJobs.length>0?await async function(e,t,s,a,o){m(e,"scheduled");for(const s of e.scheduledJobs)o({type:"message",text:`Registering cron: ${s.cron} โ€” ${s.taskDescription}`}),n.schedule(s.cron,async()=>{const n=(new Date).toISOString();s.lastRun=n,d(e),o({type:"message",text:`[cron ${s.cron}] Running: ${s.taskDescription}`});const a={...e.memory},r=a.thread_url||a.conversation_url,i=r?`${s.taskDescription}\n\n[Use thread_url from memory: ${r}]`:s.taskDescription;try{await runTask({...t,goal:i,sessionId:void 0,isScheduledRun:!0,inheritedMemory:a})}catch(e){o({type:"error",error:`Cron job failed: ${e?.message}`})}});o({type:"message",text:`${e.scheduledJobs.length} cron job(s) active. Process will stay alive. Press Ctrl+C to stop.`}),await new Promise(()=>{})}(R,e,0,0,O):m(R,"completed")),R.finalSummary=q,d(R),p(R.id,{type:"session_end",summary:q,status:R.status,ts:(new Date).toISOString()}),O({type:"done",summary:q})}catch(e){const t=e?.message||String(e);throw m(R,"failed"),R.finalSummary=`Error: ${t}`,d(R),O({type:"error",error:t}),e}finally{try{j.close()}catch{}try{await F.closeWreqSession()}catch{}}return R}async function _(s,n,a){const o=[`# Generated from task: ${n}`,`# Session: ${s.id}`,`# Generated: ${(new Date).toISOString()}`,""],{loadEvents:r}=await import("./session.js"),i=r(s.id);for(const e of i)if("tool_call"===e.type){const t=b(e.toolName,e.args);t&&o.push(t)}const l=n.toLowerCase().replace(/[^a-z0-9]+/g,"-").slice(0,40).replace(/-$/,""),c=a?t.resolve(process.cwd(),a):process.cwd();e.existsSync(c)||e.mkdirSync(c,{recursive:!0});const u=t.join(c,`${l}.flow`);return e.writeFileSync(u,o.join("\n")+"\n"),u}function b(e,t){switch(e){case"navigate":return`Go to ${t.url}`;case"click":return`Click ${t.description||t.ref}`;case"type":return`Type "${t.text}" into ${t.ref}`;case"press":return`Press ${t.key}`;case"wait":return`Wait ${t.seconds} seconds`;case"scroll":return`Scroll ${t.direction}`;case"reload":return"Reload page";case"go_back":return"Go back";case"inject_credentials":return`@inject ${t.profile_name}`;case"schedule":return`# Scheduled: ${t.cron} โ€” ${t.task_description}`;case"fetch_url":return`# Fetched: ${t.url}`;case"done":return`# Done: ${t.summary}`;default:return null}}
@@ -0,0 +1,18 @@
1
+ import { TaskSession, SessionEvent, TaskStatus } from "./types.js";
2
+ export declare function generateSessionId(): string;
3
+ export declare function createSession(goal: string, sessionId?: string): TaskSession;
4
+ export declare function loadSession(sessionId: string): TaskSession | null;
5
+ export declare function saveSessionMeta(session: TaskSession): void;
6
+ export declare function appendEvent(sessionId: string, event: SessionEvent): void;
7
+ export declare function loadEvents(sessionId: string): SessionEvent[];
8
+ export declare function listSessions(): TaskSession[];
9
+ export declare function updateSessionStatus(session: TaskSession, status: TaskStatus): void;
10
+ /**
11
+ * Rebuild the LLM message history from JSONL events.
12
+ * Used when resuming a session.
13
+ */
14
+ export declare function rebuildMessages(events: SessionEvent[]): Array<{
15
+ role: string;
16
+ content: unknown;
17
+ }>;
18
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ import t from"fs";import e from"path";function n(){return e.join(process.cwd(),".slapify/tasks")}function o(){const e=n();return t.existsSync(e)||t.mkdirSync(e,{recursive:!0}),e}function r(t){return e.join(n(),`${t}.json`)}function s(t){return e.join(n(),`${t}.jsonl`)}export function generateSessionId(){return`task-${(new Date).toISOString().replace(/[:.]/g,"-").slice(0,19)}-${Math.random().toString(36).slice(2,7)}`}export function createSession(t,e){o();const n=e||generateSessionId(),r=(new Date).toISOString(),s={id:n,goal:t,status:"running",createdAt:r,updatedAt:r,iteration:0,memory:{},scheduledJobs:[]};return saveSessionMeta(s),s}export function loadSession(e){const n=r(e);if(!t.existsSync(n))return null;try{return JSON.parse(t.readFileSync(n,"utf-8"))}catch{return null}}export function saveSessionMeta(e){o(),e.updatedAt=(new Date).toISOString(),t.writeFileSync(r(e.id),JSON.stringify(e,null,2))}export function appendEvent(e,n){o();const r=JSON.stringify(n)+"\n";t.appendFileSync(s(e),r)}export function loadEvents(e){const n=s(e);if(!t.existsSync(n))return[];return t.readFileSync(n,"utf-8").trim().split("\n").filter(Boolean).map(t=>{try{return JSON.parse(t)}catch{return null}}).filter(Boolean)}export function listSessions(){const o=n();if(!t.existsSync(o))return[];return t.readdirSync(o).filter(t=>t.endsWith(".json")).map(n=>{try{return JSON.parse(t.readFileSync(e.join(o,n),"utf-8"))}catch{return null}}).filter(Boolean).sort((t,e)=>new Date(e.updatedAt).getTime()-new Date(t.updatedAt).getTime())}export function updateSessionStatus(t,e){t.status=e,saveSessionMeta(t)}export function rebuildMessages(t){const e=[];for(const n of t)if("session_start"===n.type)e.push({role:"user",content:n.goal});else if("llm_response"===n.type){const t=[];n.text&&t.push({type:"text",text:n.text});for(const e of n.toolCalls)t.push({type:"tool-call",toolCallId:e.toolCallId,toolName:e.toolName,args:e.args});t.length>0&&e.push({role:"assistant",content:t})}else if("tool_call"===n.type||"tool_error"===n.type){const t=e[e.length-1],o={type:"tool-result",toolCallId:n.toolCallId||"",toolName:n.toolName,result:"tool_error"===n.type?`ERROR: ${n.error}`:JSON.stringify(n.result)};t&&"tool"===t.role&&Array.isArray(t.content)?t.content.push(o):e.push({role:"tool",content:[o]})}return e}
@@ -0,0 +1,253 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * All tools available to the task agent.
4
+ * The LLM decides which tools to call and when โ€” including scheduling,
5
+ * sleeping, credential injection, and signalling completion.
6
+ */
7
+ export declare const taskTools: {
8
+ navigate: import("ai").CoreTool<z.ZodObject<{
9
+ url: z.ZodString;
10
+ }, "strip", z.ZodTypeAny, {
11
+ url: string;
12
+ }, {
13
+ url: string;
14
+ }>, unknown> & {
15
+ execute: undefined;
16
+ };
17
+ get_page_state: import("ai").CoreTool<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>, unknown> & {
18
+ execute: undefined;
19
+ };
20
+ click: import("ai").CoreTool<z.ZodObject<{
21
+ ref: z.ZodString;
22
+ description: z.ZodOptional<z.ZodString>;
23
+ }, "strip", z.ZodTypeAny, {
24
+ ref: string;
25
+ description?: string | undefined;
26
+ }, {
27
+ ref: string;
28
+ description?: string | undefined;
29
+ }>, unknown> & {
30
+ execute: undefined;
31
+ };
32
+ type: import("ai").CoreTool<z.ZodObject<{
33
+ ref: z.ZodString;
34
+ text: z.ZodString;
35
+ append: z.ZodOptional<z.ZodBoolean>;
36
+ }, "strip", z.ZodTypeAny, {
37
+ text: string;
38
+ ref: string;
39
+ append?: boolean | undefined;
40
+ }, {
41
+ text: string;
42
+ ref: string;
43
+ append?: boolean | undefined;
44
+ }>, unknown> & {
45
+ execute: undefined;
46
+ };
47
+ press: import("ai").CoreTool<z.ZodObject<{
48
+ key: z.ZodString;
49
+ }, "strip", z.ZodTypeAny, {
50
+ key: string;
51
+ }, {
52
+ key: string;
53
+ }>, unknown> & {
54
+ execute: undefined;
55
+ };
56
+ scroll: import("ai").CoreTool<z.ZodObject<{
57
+ direction: z.ZodEnum<["up", "down", "left", "right"]>;
58
+ amount: z.ZodOptional<z.ZodNumber>;
59
+ }, "strip", z.ZodTypeAny, {
60
+ direction: "up" | "down" | "left" | "right";
61
+ amount?: number | undefined;
62
+ }, {
63
+ direction: "up" | "down" | "left" | "right";
64
+ amount?: number | undefined;
65
+ }>, unknown> & {
66
+ execute: undefined;
67
+ };
68
+ wait: import("ai").CoreTool<z.ZodObject<{
69
+ seconds: z.ZodNumber;
70
+ }, "strip", z.ZodTypeAny, {
71
+ seconds: number;
72
+ }, {
73
+ seconds: number;
74
+ }>, unknown> & {
75
+ execute: undefined;
76
+ };
77
+ screenshot: import("ai").CoreTool<z.ZodObject<{
78
+ description: z.ZodOptional<z.ZodString>;
79
+ }, "strip", z.ZodTypeAny, {
80
+ description?: string | undefined;
81
+ }, {
82
+ description?: string | undefined;
83
+ }>, unknown> & {
84
+ execute: undefined;
85
+ };
86
+ reload: import("ai").CoreTool<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>, unknown> & {
87
+ execute: undefined;
88
+ };
89
+ go_back: import("ai").CoreTool<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>, unknown> & {
90
+ execute: undefined;
91
+ };
92
+ list_credential_profiles: import("ai").CoreTool<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>, unknown> & {
93
+ execute: undefined;
94
+ };
95
+ inject_credentials: import("ai").CoreTool<z.ZodObject<{
96
+ profile_name: z.ZodString;
97
+ }, "strip", z.ZodTypeAny, {
98
+ profile_name: string;
99
+ }, {
100
+ profile_name: string;
101
+ }>, unknown> & {
102
+ execute: undefined;
103
+ };
104
+ fill_login_form: import("ai").CoreTool<z.ZodObject<{
105
+ profile_name: z.ZodString;
106
+ }, "strip", z.ZodTypeAny, {
107
+ profile_name: string;
108
+ }, {
109
+ profile_name: string;
110
+ }>, unknown> & {
111
+ execute: undefined;
112
+ };
113
+ remember: import("ai").CoreTool<z.ZodObject<{
114
+ key: z.ZodString;
115
+ value: z.ZodString;
116
+ }, "strip", z.ZodTypeAny, {
117
+ key: string;
118
+ value: string;
119
+ }, {
120
+ key: string;
121
+ value: string;
122
+ }>, unknown> & {
123
+ execute: undefined;
124
+ };
125
+ recall: import("ai").CoreTool<z.ZodObject<{
126
+ key: z.ZodString;
127
+ }, "strip", z.ZodTypeAny, {
128
+ key: string;
129
+ }, {
130
+ key: string;
131
+ }>, unknown> & {
132
+ execute: undefined;
133
+ };
134
+ list_memories: import("ai").CoreTool<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>, unknown> & {
135
+ execute: undefined;
136
+ };
137
+ schedule: import("ai").CoreTool<z.ZodObject<{
138
+ cron: z.ZodString;
139
+ task_description: z.ZodString;
140
+ }, "strip", z.ZodTypeAny, {
141
+ cron: string;
142
+ task_description: string;
143
+ }, {
144
+ cron: string;
145
+ task_description: string;
146
+ }>, unknown> & {
147
+ execute: undefined;
148
+ };
149
+ sleep_until: import("ai").CoreTool<z.ZodObject<{
150
+ until: z.ZodString;
151
+ reason: z.ZodOptional<z.ZodString>;
152
+ }, "strip", z.ZodTypeAny, {
153
+ until: string;
154
+ reason?: string | undefined;
155
+ }, {
156
+ until: string;
157
+ reason?: string | undefined;
158
+ }>, unknown> & {
159
+ execute: undefined;
160
+ };
161
+ solve_captcha: import("ai").CoreTool<z.ZodObject<{
162
+ type: z.ZodOptional<z.ZodEnum<["recaptcha_v2", "image", "text", "auto"]>>;
163
+ }, "strip", z.ZodTypeAny, {
164
+ type?: "text" | "image" | "auto" | "recaptcha_v2" | undefined;
165
+ }, {
166
+ type?: "text" | "image" | "auto" | "recaptcha_v2" | undefined;
167
+ }>, unknown> & {
168
+ execute: undefined;
169
+ };
170
+ fetch_url: import("ai").CoreTool<z.ZodObject<{
171
+ url: z.ZodString;
172
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
173
+ }, "strip", z.ZodTypeAny, {
174
+ url: string;
175
+ headers?: Record<string, string> | undefined;
176
+ }, {
177
+ url: string;
178
+ headers?: Record<string, string> | undefined;
179
+ }>, unknown> & {
180
+ execute: undefined;
181
+ };
182
+ status_update: import("ai").CoreTool<z.ZodObject<{
183
+ message: z.ZodString;
184
+ }, "strip", z.ZodTypeAny, {
185
+ message: string;
186
+ }, {
187
+ message: string;
188
+ }>, unknown> & {
189
+ execute: undefined;
190
+ };
191
+ ask_user: import("ai").CoreTool<z.ZodObject<{
192
+ question: z.ZodString;
193
+ hint: z.ZodOptional<z.ZodString>;
194
+ }, "strip", z.ZodTypeAny, {
195
+ question: string;
196
+ hint?: string | undefined;
197
+ }, {
198
+ question: string;
199
+ hint?: string | undefined;
200
+ }>, unknown> & {
201
+ execute: undefined;
202
+ };
203
+ save_credentials: import("ai").CoreTool<z.ZodObject<{
204
+ profile_name: z.ZodString;
205
+ type: z.ZodEnum<["inject", "login-form"]>;
206
+ username: z.ZodOptional<z.ZodString>;
207
+ password: z.ZodOptional<z.ZodString>;
208
+ capture_from_browser: z.ZodOptional<z.ZodBoolean>;
209
+ }, "strip", z.ZodTypeAny, {
210
+ type: "login-form" | "inject";
211
+ profile_name: string;
212
+ username?: string | undefined;
213
+ password?: string | undefined;
214
+ capture_from_browser?: boolean | undefined;
215
+ }, {
216
+ type: "login-form" | "inject";
217
+ profile_name: string;
218
+ username?: string | undefined;
219
+ password?: string | undefined;
220
+ capture_from_browser?: boolean | undefined;
221
+ }>, unknown> & {
222
+ execute: undefined;
223
+ };
224
+ perf_audit: import("ai").CoreTool<z.ZodObject<{
225
+ url: z.ZodString;
226
+ lighthouse: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
227
+ react_scan: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
228
+ }, "strip", z.ZodTypeAny, {
229
+ url: string;
230
+ lighthouse: boolean;
231
+ react_scan: boolean;
232
+ }, {
233
+ url: string;
234
+ lighthouse?: boolean | undefined;
235
+ react_scan?: boolean | undefined;
236
+ }>, unknown> & {
237
+ execute: undefined;
238
+ };
239
+ done: import("ai").CoreTool<z.ZodObject<{
240
+ summary: z.ZodString;
241
+ save_flow: z.ZodOptional<z.ZodBoolean>;
242
+ }, "strip", z.ZodTypeAny, {
243
+ summary: string;
244
+ save_flow?: boolean | undefined;
245
+ }, {
246
+ summary: string;
247
+ save_flow?: boolean | undefined;
248
+ }>, unknown> & {
249
+ execute: undefined;
250
+ };
251
+ };
252
+ export type TaskToolName = keyof typeof taskTools;
253
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ import{tool as e}from"ai";import{z as t}from"zod";export const taskTools={navigate:e({description:"Navigate the browser to a URL. Always call get_page_state() afterwards to see the result.",parameters:t.object({url:t.string().describe("Full URL including https://")})}),get_page_state:e({description:"Get the current browser state: URL, page title, and an accessibility snapshot of all interactive elements with their ref IDs. Use ref IDs with click/type tools.",parameters:t.object({})}),click:e({description:"Click an element identified by its ref ID from get_page_state(). Use for buttons, links, checkboxes, etc.",parameters:t.object({ref:t.string().describe("The ref ID of the element to click"),description:t.string().optional().describe("Human-readable description of what you're clicking")})}),type:e({description:"Type text into an input field identified by its ref ID. Clears the field first unless append is true.",parameters:t.object({ref:t.string().describe("The ref ID of the input element"),text:t.string().describe("The text to type"),append:t.boolean().optional().describe("If true, append to existing text instead of replacing")})}),press:e({description:"Press a keyboard key or key combination (e.g. Enter, Tab, Escape, Control+A, ArrowDown).",parameters:t.object({key:t.string().describe("Key or key combo to press")})}),scroll:e({description:"Scroll the page in a direction.",parameters:t.object({direction:t.enum(["up","down","left","right"]),amount:t.number().optional().describe("Pixels to scroll (default 300)")})}),wait:e({description:"Wait for a specified number of seconds. Use when waiting for page loads, animations, or time-sensitive operations.",parameters:t.object({seconds:t.number().min(.1).max(60).describe("Seconds to wait")})}),screenshot:e({description:"Take a screenshot of the current browser state and return a description of what is visible.",parameters:t.object({description:t.string().optional().describe("Why you are taking this screenshot")})}),reload:e({description:"Reload the current page.",parameters:t.object({})}),go_back:e({description:"Navigate back to the previous page.",parameters:t.object({})}),list_credential_profiles:e({description:"List all available credential profiles and their types (e.g. login-form, inject, oauth). Call this when you reach a login page or need to authenticate โ€” the agent picks the right profile automatically.",parameters:t.object({})}),inject_credentials:e({description:"Inject a credential profile into the browser (sets cookies, localStorage, sessionStorage) and reload. Use for 'inject' type profiles. After injection call get_page_state() to verify login status. If the injected session is expired (redirected to login page), fall back to fill_login_form.",parameters:t.object({profile_name:t.string().describe("The name of the credential profile to inject")})}),fill_login_form:e({description:"Fill and submit a login form using credentials from a profile. Use for 'login-form' type profiles. After verifying login succeeded, call save_credentials(capture_from_browser: true) to save the session.",parameters:t.object({profile_name:t.string().describe("The name of the credential profile to use")})}),remember:e({description:"Store a piece of information in persistent memory that survives across iterations and sessions. Use for important findings, extracted data, or state you'll need later.",parameters:t.object({key:t.string().describe("Unique key for this memory"),value:t.string().describe("Value to store (use JSON string for structured data)")})}),recall:e({description:"Retrieve a previously stored memory value by key.",parameters:t.object({key:t.string().describe("The memory key to retrieve")})}),list_memories:e({description:"List all keys currently stored in memory.",parameters:t.object({})}),schedule:e({description:"Schedule this task (or a sub-task) to run on a recurring cron schedule. For example: monitor every 30 minutes, check daily at 9am, etc. The process will stay alive and re-run the goal at each interval. Examples: '*/30 * * * *' every 30 min, '0 9 * * *' every day at 9am.",parameters:t.object({cron:t.string().describe("Standard cron expression (5 fields: min hour dom month dow)"),task_description:t.string().describe("What to do when this schedule fires (can be same as main goal)")})}),sleep_until:e({description:"Pause the agent until a specific time or after a duration, then continue. Use when you need to wait before doing something (e.g. 'try again in 5 minutes', 'wait until 2pm'). Provide an ISO datetime string or a natural phrase like '5 minutes', '1 hour', 'tomorrow 9am'.",parameters:t.object({until:t.string().describe("ISO datetime string OR duration phrase like '5 minutes', '2 hours', 'tomorrow 9am'"),reason:t.string().optional().describe("Why the agent is sleeping")})}),solve_captcha:e({description:"Attempt to automatically solve a CAPTCHA on the current page. Tries: reCAPTCHA v2 checkbox click, audio challenge fallback. Call get_page_state() first to confirm a CAPTCHA is present. Returns whether it succeeded and the updated page state.",parameters:t.object({type:t.enum(["recaptcha_v2","image","text","auto"]).optional().describe("CAPTCHA type hint โ€” use 'auto' if unsure")})}),fetch_url:e({description:"Make a direct HTTP GET request and return the response text or JSON. Uses Chrome TLS fingerprint impersonation (wreq-js) to bypass Cloudflare and bot detection. Much faster than browser navigation. Try this first for any data lookup โ€” public APIs for prices, weather, news, finance, etc. usually return JSON directly.",parameters:t.object({url:t.string().describe("URL to fetch"),headers:t.record(t.string()).optional().describe("Optional HTTP headers as key-value pairs")})}),status_update:e({description:"Post a visible status message to the user. Use this for long-running tasks to keep the user informed โ€” e.g. when starting a scheduled check, when something interesting is found, or when waiting before the next run. This always shows regardless of debug mode.",parameters:t.object({message:t.string().describe("The message to show the user")})}),ask_user:e({description:"Ask the user a question and wait for their response. Use when you need information that is not available elsewhere โ€” e.g. an OTP, a 2FA code, a missing password, a clarification about what to do next, or confirmation before a destructive action. The task pauses until the user replies. Returns the user's answer as a string.",parameters:t.object({question:t.string().describe("The question to ask the user"),hint:t.string().optional().describe("Optional hint shown below the question, e.g. 'Check your phone for the OTP'")})}),save_credentials:e({description:"Save credentials or session cookies to the .slapify/credentials.yaml file so they can be reused in future sessions. Call this after successfully authenticating, or after the user confirms they want to save. Use type='inject' for cookies/localStorage/sessionStorage. Use type='login-form' for username+password.",parameters:t.object({profile_name:t.string().describe("Name for this credential profile, e.g. 'linkedin', 'gmail'"),type:t.enum(["inject","login-form"]),username:t.string().optional().describe("Username or email (for login-form type)"),password:t.string().optional().describe("Password (for login-form type)"),capture_from_browser:t.boolean().optional().describe("If true, capture current browser cookies + localStorage + sessionStorage automatically")})}),perf_audit:e({description:"Run a full performance audit on a URL. Returns: real-user metrics (FCP, LCP, CLS, TTFB), scores (Performance, Accessibility, SEO, Best Practices 0-100), framework detection, and re-render analysis โ€” including simulated user interactions that click buttons/tabs to measure how much DOM activity each interaction triggers. Use when the user asks about performance, speed, scores, or page health. The deep audit runs in an isolated browser so the current session is not affected.",parameters:t.object({url:t.string().describe("URL to audit"),lighthouse:t.boolean().optional().default(!0).describe("Run deep performance scoring (default: true)"),react_scan:t.boolean().optional().default(!0).describe("Run framework & re-render analysis including interaction tests (default: true)")})}),done:e({description:"Signal that the task is complete. Provide a clear summary of everything that was accomplished. If save_flow is true, the agent's action history will be saved as a .flow file.",parameters:t.object({summary:t.string().describe("Detailed summary of what was accomplished"),save_flow:t.boolean().optional().describe("If true, save the action history as a reusable .flow file")})})};
@@ -0,0 +1,153 @@
1
+ export type TaskStatus = "running" | "completed" | "failed" | "sleeping" | "scheduled";
2
+ export interface TaskSession {
3
+ id: string;
4
+ goal: string;
5
+ status: TaskStatus;
6
+ createdAt: string;
7
+ updatedAt: string;
8
+ iteration: number;
9
+ memory: Record<string, string>;
10
+ scheduledJobs: ScheduledJob[];
11
+ finalSummary?: string;
12
+ savedFlowPath?: string;
13
+ /**
14
+ * All performance audit results collected this session.
15
+ * Each call to perf_audit appends here, supporting multi-page comparisons.
16
+ */
17
+ perfAudits?: import("../perf/audit.js").PerfAuditResult[];
18
+ /** @deprecated use perfAudits[0] โ€” kept for backwards compat with old reports */
19
+ perfAudit?: import("../perf/audit.js").PerfAuditResult;
20
+ }
21
+ export interface ScheduledJob {
22
+ id: string;
23
+ cron: string;
24
+ taskDescription: string;
25
+ createdAt: string;
26
+ lastRun?: string;
27
+ nextRun?: string;
28
+ }
29
+ export type SessionEvent = {
30
+ type: "session_start";
31
+ goal: string;
32
+ ts: string;
33
+ } | {
34
+ type: "iteration_start";
35
+ iteration: number;
36
+ ts: string;
37
+ } | {
38
+ type: "llm_response";
39
+ text: string;
40
+ toolCalls: ToolCallRecord[];
41
+ ts: string;
42
+ } | {
43
+ type: "tool_call";
44
+ toolName: string;
45
+ args: Record<string, unknown>;
46
+ result: unknown;
47
+ ts: string;
48
+ } | {
49
+ type: "tool_error";
50
+ toolName: string;
51
+ args: Record<string, unknown>;
52
+ error: string;
53
+ ts: string;
54
+ } | {
55
+ type: "memory_update";
56
+ key: string;
57
+ value: string;
58
+ ts: string;
59
+ } | {
60
+ type: "scheduled";
61
+ cron: string;
62
+ task: string;
63
+ ts: string;
64
+ } | {
65
+ type: "sleeping_until";
66
+ until: string;
67
+ ts: string;
68
+ } | {
69
+ type: "session_end";
70
+ summary: string;
71
+ status: TaskStatus;
72
+ ts: string;
73
+ } | {
74
+ type: "context_compacted";
75
+ fromMessages: number;
76
+ toMessages: number;
77
+ ts: string;
78
+ };
79
+ export interface ToolCallRecord {
80
+ toolCallId: string;
81
+ toolName: string;
82
+ args: Record<string, unknown>;
83
+ }
84
+ export interface TaskRunOptions {
85
+ goal: string;
86
+ sessionId?: string;
87
+ headed?: boolean;
88
+ executablePath?: string;
89
+ saveFlow?: boolean;
90
+ /** Directory to write the .flow file into (default: cwd) */
91
+ flowOutputDir?: string;
92
+ maxIterations?: number;
93
+ onEvent?: (event: TaskEvent) => void;
94
+ onSessionUpdate?: (session: TaskSession) => void;
95
+ /** Called when the agent needs input from the user. Must resolve with the user's answer. */
96
+ onHumanInput?: (question: string, hint?: string) => Promise<string>;
97
+ /**
98
+ * When true this is a scheduled sub-run spawned by a cron job.
99
+ * The agent must NOT call schedule() again โ€” it would create runaway duplicates.
100
+ */
101
+ isScheduledRun?: boolean;
102
+ /**
103
+ * Memory key/value pairs inherited from the parent session.
104
+ * Injected into the sub-run so it knows the thread URL, last message, etc.
105
+ */
106
+ inheritedMemory?: Record<string, string>;
107
+ }
108
+ export type TaskEvent = {
109
+ type: "thinking";
110
+ } | {
111
+ type: "tool_start";
112
+ toolName: string;
113
+ args: Record<string, unknown>;
114
+ } | {
115
+ type: "tool_done";
116
+ toolName: string;
117
+ result: string;
118
+ } | {
119
+ type: "tool_error";
120
+ toolName: string;
121
+ error: string;
122
+ } | {
123
+ type: "message";
124
+ text: string;
125
+ } | {
126
+ type: "status_update";
127
+ message: string;
128
+ } | {
129
+ type: "human_input_needed";
130
+ question: string;
131
+ hint?: string;
132
+ } | {
133
+ type: "credentials_saved";
134
+ profileName: string;
135
+ credType: string;
136
+ } | {
137
+ type: "scheduled";
138
+ cron: string;
139
+ task: string;
140
+ } | {
141
+ type: "sleeping";
142
+ until: string;
143
+ } | {
144
+ type: "done";
145
+ summary: string;
146
+ } | {
147
+ type: "flow_saved";
148
+ path: string;
149
+ } | {
150
+ type: "error";
151
+ error: string;
152
+ };
153
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ export{};
package/dist/types.d.ts CHANGED
@@ -90,6 +90,8 @@ export interface TestResult {
90
90
  endTime: Date;
91
91
  autoHandled: string[];
92
92
  assumptions: string[];
93
+ /** Populated when --performance flag is used */
94
+ perfAudit?: import("./perf/audit.js").PerfAuditResult;
93
95
  }
94
96
  export interface BrowserState {
95
97
  url: string;
package/dist/types.js CHANGED
@@ -1,2 +1 @@
1
- export {};
2
- //# sourceMappingURL=types.js.map
1
+ export{};