agenttwin 0.3.0 → 0.3.2
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/dist/api.js +802 -49
- package/dist/cli.js +860 -738
- package/dist/dashboard/assets/dashboard/agent-home.js +3 -3
- package/dist/dashboard/assets/dashboard/dashboard.css +2 -0
- package/dist/dashboard/assets/dashboard/dashboard.js +20 -7
- package/dist/dashboard/index.html +2 -2
- package/dist/gateway.js +802 -49
- package/package.json +1 -1
|
@@ -48,7 +48,7 @@ function renderReceipts(){
|
|
|
48
48
|
$('#receipts-count').textContent=failed.length||'';
|
|
49
49
|
$('#receipts-spin').hidden=!liveJobs().length;
|
|
50
50
|
if(runsLoadError&&!runs.length){$('#receipts-body').innerHTML=receiptError();return;}
|
|
51
|
-
const row=r=>`<button type="button" class="review-row" data-open-receipt="${esc(r.runId)}"><span class="r-ico">${statusIco(r.status)}</span><strong>${esc(title(r))}</strong><span class="ref" title="${esc(window.agentTwinDashboard?.getReceiptSource(r.runId))}">${esc(
|
|
51
|
+
const row=r=>`<button type="button" class="review-row" data-open-receipt="${esc(r.runId)}"><span class="r-ico">${statusIco(r.status)}</span><strong>${esc(title(r))}</strong><span class="ref" title="${esc(window.agentTwinDashboard?.getReceiptSource(r.runId))}">${r.runtime==='local'?'CLI':r.runtime==='e2b'?'Cloud':'Imported'} · ${r.git?.head?esc(r.git.head.slice(0,8))+(r.git.dirty?' + changes':''):esc(String(r.runId).slice(0,8))}</span><span class="r-status ${esc(r.status)}">${esc(r.status)}</span><span class="r-time">${esc(rel(r.createdAt))}</span></button>`;
|
|
52
52
|
let html='';
|
|
53
53
|
if(failed.length)html+=`<p class="group-label warn">Needs review · <b>${failed.length}</b> failed receipt${failed.length===1?'':'s'}</p>${failed.slice(0,2).map(row).join('')}`;
|
|
54
54
|
if(rest.length)html+=`<p class="group-label">Passed · <b>${rest.length}</b></p>${rest.slice(0,4).map(row).join('')}`;
|
|
@@ -70,7 +70,7 @@ function renderGit(){
|
|
|
70
70
|
const days=Array.from({length:14},(_,i)=>{const day=new Date(Date.now()-(13-i)*86400000).toISOString().slice(0,10);return {day,count:g.commits.filter(c=>c.date.slice(0,10)===day).length};});
|
|
71
71
|
const max=Math.max(1,...days.map(d=>d.count));
|
|
72
72
|
const changes=(d.workflows??[]).filter(w=>g.changedFiles.includes((w.entrypoint??'').split('#')[0]));
|
|
73
|
-
$('#git-body').innerHTML=`<div class="git-total"><strong>${g.commits.length}${g.commits.length===100?'+':''}</strong><span>commits · last 14 days</span></div><div class="git-bars" aria-label="Commit activity over 14 days">${days.map(d=>`<span style="--bar:${Math.max(5,d.count/max*100)}%" title="${esc(d.day)}: ${d.count} commits"></span>`).join('')}</div><div class="git-foot"><span>${g.totalCommits} commits in repository</span><code>${esc(g.head.slice(0,8))}${g.dirty?' + changes':''}</code></div><p class="w-empty">${changes.length?changes.length+' workflow entrypoints changed since the previous sync. Retest to record new outcomes.':'No workflow entrypoint changes detected since the previous sync.'}</p>`;
|
|
73
|
+
$('#git-body').innerHTML=`<div class="git-total"><strong>${g.commits.length}${g.commits.length===100?'+':''}</strong><span>commits · last 14 days</span></div><div class="git-bars" aria-label="Commit activity over 14 days">${days.map(d=>`<span style="--bar:${Math.max(5,d.count/max*100)}%" title="${esc(d.day)}: ${d.count} commits"></span>`).join('')}</div><div class="git-foot"><span>${g.totalCommits} commits in repository</span><code>${esc(g.head.slice(0,8))}${g.dirty?' + changes':''}</code></div><p class="w-empty">${!g.previousHead?'First Git snapshot. Change comparisons begin with the next sync.':changes.length?changes.length+' workflow entrypoints changed since the previous sync. Retest to record new outcomes.':'No workflow entrypoint changes detected since the previous sync.'}</p>`;
|
|
74
74
|
}
|
|
75
75
|
function renderServices(){
|
|
76
76
|
const events=runs.flatMap(r=>(r.events??[]).map(e=>({r,e}))).slice(0,5);
|
|
@@ -250,7 +250,7 @@ function onHostedJobs(){
|
|
|
250
250
|
renderAll();
|
|
251
251
|
}
|
|
252
252
|
if($('#home-view')){
|
|
253
|
-
|
|
253
|
+
|
|
254
254
|
|
|
255
255
|
composerInit();
|
|
256
256
|
topbarInit();
|
|
@@ -187,3 +187,5 @@ button,.upload{background:#202329;border-color:#353944;color:var(--ink);border-r
|
|
|
187
187
|
.discovery-onboarding{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(0,1fr);gap:24px;margin:0 0 28px}.discovery-onboarding .terminal-panel{position:static;min-width:0}.discovery-onboarding .workflow-area{min-width:0}.workflow-search{display:block;margin:18px 0}.workflow-search input{display:block;width:100%;margin-top:8px}#workflow-candidates{max-height:650px;overflow:auto}.candidate-row{gap:24px}.candidate-row>div{min-width:0}.candidate-row .mono{overflow-wrap:anywhere}@media(max-width:900px){.discovery-onboarding{grid-template-columns:1fr}}
|
|
188
188
|
|
|
189
189
|
#environment-workflows{max-height:320px;overflow:auto}.discovery-onboarding .terminal-boundary code{overflow-wrap:anywhere}
|
|
190
|
+
|
|
191
|
+
.sidebar-workflow-group{margin:4px 0 12px}.sidebar-workflow-group summary{cursor:pointer;color:var(--text-muted,#93949f);padding:8px 10px;font-size:12px;display:flex;justify-content:space-between}.sidebar-workflow-group summary span{opacity:.65}.sidebar-workflow-group .sidebar-workflow{padding-left:20px;line-height:1.5;white-space:normal;text-align:left}.sidebar-workflow-group summary:hover{color:var(--text,#eee)}
|
|
@@ -91,13 +91,25 @@ document.querySelector('.project-tabs').addEventListener('click',event=>{const b
|
|
|
91
91
|
const downloadFile=(name,text)=>{const url=URL.createObjectURL(new Blob([text],{type:name.endsWith('.json')?'application/json':'text/plain'}));const a=document.createElement('a');a.href=url;a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);};
|
|
92
92
|
let environmentTools=[], environmentWorkflows=[], inventoryImported=false;
|
|
93
93
|
let environmentProject='Your project', environmentGit=null;
|
|
94
|
+
// Names describe the callable operation; IDs and entrypoints remain unchanged.
|
|
95
|
+
const workflowIntentNames={sendDriveFile:'Send a Drive file by email',gmailForwardMessage:'Forward an email',modifyGmailMessages:'Update email labels',trashGmailMessages:'Move emails to trash',gmailSendMessage:'Send an email',gmailCreateDraft:'Create an email draft',googleEventsRaw:'Read calendar events',findFreeSlots:'Find available meeting times',calendarHold:'Reserve a calendar slot',mutateCalendarEvent:'Update a calendar event'};
|
|
96
|
+
function readableName(value){return String(value).replace(/([a-z0-9])([A-Z])/g,'$1 $2').replace(/([A-Z])([A-Z][a-z])/g,'$1 $2').replace(/[_-]+/g,' ').replace(/\s+/g,' ').trim();}
|
|
97
|
+
function workflowName(w){const fn=(w.entrypoint??'').split('#')[1];if(workflowIntentNames[fn])return workflowIntentNames[fn];let text=readableName(w.title??w.name??fn??w.id);if(/routes?$/i.test(text))text=text.replace(/ routes?$/i,' API routes');return text.replace(/^./,c=>c.toUpperCase()).replace(/\bgmail\b/gi,'Gmail');}
|
|
98
|
+
function workflowGroup(w){const fn=(w.entrypoint??'').split('#')[1]??'';if(/(?:^|\/)routes\//.test(w.entrypoint??''))return 'API routes';if(/Drive|File/.test(fn))return 'Files';if(/gmail|Gmail|Mail|Email/.test(fn))return 'Email';if(/calendar|Calendar|Events|FreeSlots|Meeting/.test(fn))return 'Calendar';return 'Other workflows';}
|
|
99
|
+
function sortedWorkflows(list){const order=['Email','Calendar','Files','API routes','Other workflows'];return [...list].sort((a,b)=>order.indexOf(workflowGroup(a))-order.indexOf(workflowGroup(b))||workflowName(a).localeCompare(workflowName(b)));}
|
|
94
100
|
function controlPath(tool){
|
|
95
101
|
const mode=tool.interface;
|
|
96
102
|
const title=tool.title.toLowerCase();
|
|
103
|
+
if(tool.kind==='database')return ['Database','Discovered database dependency. Review the workflow’s queries and starting state before configuring its runtime.'];
|
|
104
|
+
if(tool.kind==='ai')return ['Model provider','Discovered model dependency. Choose explicit test responses and assertions; discovery does not reproduce model behavior.'];
|
|
105
|
+
if(title==='kernel')return ['Browser service','Kernel SDK detected. Browser execution needs a reviewed browser workflow and compatible runner.'];
|
|
106
|
+
if(title==='e2b')return ['Cloud runtime SDK','The project declares E2B. This does not launch a VM or require AgentTwin tests to use cloud execution.'];
|
|
107
|
+
if(title==='composio')return ['Integration gateway','Composio SDK detected. Review which connected services and operations this workflow invokes.'];
|
|
108
|
+
if(title==='1password')return ['Secret manager','1Password SDK detected. Test fixtures must replace secrets; no vault access is implied.'];
|
|
97
109
|
if(tool.category==='access')return ['Configuration','Connector configuration was detected. This does not establish application usage or verify account access.'];
|
|
98
110
|
if(mode==='desktop')return ['Desktop','Needs desktop automation and a compatible hosted OS. Native Apple apps require macOS; this E2B runtime is Linux. Desktop execution is not implemented.'];
|
|
99
111
|
if(mode==='browser')return ['Browser','No API is required to identify this app. Browser automation in the hosted runtime is planned, not implemented.'];
|
|
100
|
-
if(mode==='terminal'||tool.kind==='runtime')return ['Terminal','Discovered tooling is part of the environment. Bundled Node workflows
|
|
112
|
+
if(mode==='terminal'||tool.kind==='runtime')return ['Terminal','Discovered tooling is part of the environment. Bundled Node workflows run through the CLI; arbitrary command-line tool replication is not yet supported.'];
|
|
101
113
|
if(title==='slack')return ['API subset','Slack message/channel/history operations have a bounded stateful adapter. Map the actual operations before executing a workflow.'];
|
|
102
114
|
if(/postgres/.test(title))return ['Real service','PostgreSQL 15 is available in hosted worlds with synthetic seed SQL and state assertions. Discovery does not provision it automatically.'];
|
|
103
115
|
if(title==='redis')return ['Real service','Redis is available in hosted worlds with fresh credentials and string-key state assertions.'];
|
|
@@ -108,7 +120,7 @@ function renderEnvironment(){
|
|
|
108
120
|
const ready=environmentTools.length>0||environmentWorkflows.length>0;
|
|
109
121
|
$("#environment-view").classList.toggle("has-discovery",ready);
|
|
110
122
|
$("#environment-title").textContent=inventoryImported?environmentProject+" environment":ready?"Your environment":"A test world for your agent.";
|
|
111
|
-
$('#environment-subtitle').textContent=inventoryImported?`${environmentTools.length}
|
|
123
|
+
$('#environment-subtitle').textContent=inventoryImported?`${environmentTools.length} project resources discovered. Choose a workflow to review what its test world needs.`:'Discover your project. Test what your agent does before it touches production.';
|
|
112
124
|
$('.discovery-onboarding').hidden=inventoryImported;
|
|
113
125
|
$('#configure-run').textContent=environmentWorkflows.length?`View all ${environmentWorkflows.length} workflows →`:'Review workflows →';
|
|
114
126
|
$('#discovery-workflows-title').textContent=inventoryImported?'Workflow candidates':'Discovery';
|
|
@@ -124,7 +136,7 @@ function renderEnvironment(){
|
|
|
124
136
|
$('#inventory-context').textContent=tools.length?`${tools.length} resources in your environment. Review coverage before execution.`:'No software selected. Select software or import the inventory from your project.';
|
|
125
137
|
layoutMap();
|
|
126
138
|
saveEnvironment();
|
|
127
|
-
$('#environment-workflows').innerHTML=environmentWorkflows.length?environmentWorkflows.slice(0,3).map(w=>`<article class="tool-entry"><header><strong>${escape(w
|
|
139
|
+
$('#environment-workflows').innerHTML=environmentWorkflows.length?environmentWorkflows.slice(0,3).map(w=>`<article class="tool-entry"><header><strong>${escape(workflowName(w))}</strong><span class="tool-route">Candidate</span></header><small>${escape(w.id)}</small><p>${escape(w.entrypoint||"Entrypoint needs review")}</p></article>`).join(''):'<div class="workflow-empty"><img src="assets/icons/ui/git-branch.svg" alt=""><h3>No workflows yet</h3><p>Import project discovery to review candidate code paths.</p></div>';
|
|
128
140
|
if(!environmentTools.length)$('#terminal-output').innerHTML='<p>Discovery stays on your machine until you sync.</p>';
|
|
129
141
|
if(environmentTools.length)$('#terminal-output').innerHTML=`<p>${inventoryImported?'Imported discovery inventory.':'Added user declarations; no scan was performed.'}</p><p>${environmentTools.length} apps / tools · ${environmentWorkflows.length} workflow candidates</p>`;
|
|
130
142
|
}
|
|
@@ -140,7 +152,8 @@ function applyDiscovery(inventory){
|
|
|
140
152
|
environmentProject=typeof inventory.name==='string'?inventory.name:'Your project';
|
|
141
153
|
const revision=`${environmentProject}:${inventory.generatedAt??signature}`;
|
|
142
154
|
if(discoveryRevision!==revision){dismissedDiscovery.clear();discoveryRevision=revision;}
|
|
143
|
-
const
|
|
155
|
+
const toolOrder=['Gmail','Google Calendar','Google Drive','Google Docs','Google Sheets','Composio','Kernel'];
|
|
156
|
+
const detected=inventory.tools.filter(t=>!dismissedDiscovery.has(t.id??t.title)).map(t=>({...t,declared:false,catalog:false})).sort((a,b)=>{const rank=t=>toolOrder.includes(t.title)?toolOrder.indexOf(t.title):t.category==='tooling'?100:20;return rank(a)-rank(b)||a.title.localeCompare(b.title);});
|
|
144
157
|
environmentTools=[...detected,...environmentTools.filter(t=>t.declared&&!detected.some(d=>d.title.toLowerCase()===t.title.toLowerCase()))];
|
|
145
158
|
environmentWorkflows=Array.isArray(inventory.workflows)?inventory.workflows.filter(w=>w&&typeof w.id==='string'):[];
|
|
146
159
|
inventoryImported=true;mapFocused=-1;renderEnvironment();renderConnectors();renderProjectSidebar();
|
|
@@ -313,8 +326,8 @@ function renderWorkspace(){
|
|
|
313
326
|
$('#environment-coverage').innerHTML=`<span><b>${environmentTools.length}</b> selected resources</span><span><b>${supported.length}</b> with runtime support</span><span><b>${gaps.length}</b> require an adapter or runtime</span><span><b>${configs.length}</b> configuration only</span>`;
|
|
314
327
|
$('#configure-view').classList.toggle('has-workflows',environmentWorkflows.length>0);
|
|
315
328
|
if(!environmentWorkflows.length){$('#workflow-workspace').innerHTML='<div class="workflow-intro"><span class="intro-mark" aria-hidden="true">↳</span><h2>Start with what your agent does.</h2><p>Discover your project to find code paths and their dependencies. Then choose a workflow to test.</p><button type="button" data-action="environment">Discover workflows →</button></div>';return;}
|
|
316
|
-
const visible=environmentWorkflows.filter(w=>JSON.stringify(w).toLowerCase().includes(workflowQuery.toLowerCase()));const pageSize=10;workflowPage=Math.min(workflowPage,Math.max(0,Math.ceil(visible.length/pageSize)-1));const page=visible.slice(workflowPage*pageSize,(workflowPage+1)*pageSize);
|
|
317
|
-
$('#workflow-workspace').innerHTML=`<section class="workflow-register"><div class="section-title"><h2>Your workflows</h2><span>${visible.length} of ${environmentWorkflows.length}</span></div><label class="workflow-search">Find a workflow<input id="workflow-search" type="search" placeholder="Search name, entrypoint or operation" value="${escape(workflowQuery)}"></label><div id="workflow-candidates">${visible.length?page.map(w=>`<article class="candidate-row"><div><h3>${escape(w
|
|
329
|
+
const visible=sortedWorkflows(environmentWorkflows).filter(w=>(workflowName(w)+' '+workflowGroup(w)+' '+JSON.stringify(w)).toLowerCase().includes(workflowQuery.toLowerCase()));const pageSize=10;workflowPage=Math.min(workflowPage,Math.max(0,Math.ceil(visible.length/pageSize)-1));const page=visible.slice(workflowPage*pageSize,(workflowPage+1)*pageSize);
|
|
330
|
+
$('#workflow-workspace').innerHTML=`<section class="workflow-register"><div class="section-title"><h2>Your workflows</h2><span>${visible.length} of ${environmentWorkflows.length}</span></div><label class="workflow-search">Find a workflow<input id="workflow-search" type="search" placeholder="Search name, entrypoint or operation" value="${escape(workflowQuery)}"></label><div id="workflow-candidates">${visible.length?page.map(w=>`<article class="candidate-row"><div><h3>${escape(workflowName(w))}</h3><p class="muted">${escape(workflowGroup(w))}${w.tools.length?' · '+escape(w.tools.map(id=>environmentTools.find(t=>t.id===id)?.title??id).join(', ')):' · Dependencies need review'}</p><p class="mono">${escape(w.entrypoint||w.id)}</p><p class="muted">${w.supported?'Dependency links detected':'Dependencies need review'} · ${workflowEvidence(w)}</p></div><div class="actions">${runs.some(r=>r.workflowId===w.id)?`<button type="button" data-candidate-results="${escape(w.id)}">View ${runs.filter(r=>r.workflowId===w.id).length} run results →</button>`:''}<button type="button" data-prepare-candidate="${escape(w.id)}">Prepare workflow →</button></div></article>`).join(''):'<p class="coverage">No workflows match your search.</p>'}</div>${visible.length>pageSize?`<div class="workflow-pagination"><button type="button" data-workflow-page="-1" ${workflowPage===0?'disabled':''}>Previous</button><span>Page ${workflowPage+1} of ${Math.ceil(visible.length/pageSize)}</span><button type="button" data-workflow-page="1" ${(workflowPage+1)*pageSize>=visible.length?'disabled':''}>Next</button></div>`:''}</section>`;if(preparation)$('#workflow-workspace').prepend(preparation);
|
|
318
331
|
}
|
|
319
332
|
$('#main').addEventListener('input',event=>{if(event.target.id!=='workflow-search')return;workflowQuery=event.target.value;workflowPage=0;const cursor=event.target.selectionStart;renderWorkspace();$('#workflow-search').focus();try{$('#workflow-search').setSelectionRange(cursor,cursor);}catch{}});
|
|
320
333
|
$('#main').addEventListener('click',event=>{
|
|
@@ -408,7 +421,7 @@ document.querySelector('.topbar .brand').addEventListener('click',event=>{if(eve
|
|
|
408
421
|
function renderProjectSidebar(){
|
|
409
422
|
const target=document.getElementById('sidebar-projects');if(!target)return;
|
|
410
423
|
const projects=window.agentTwinProjects?.length?window.agentTwinProjects:(inventoryImported?[{name:environmentProject}]:[]);
|
|
411
|
-
target.innerHTML='<p class="sidebar-label">Repositories</p>'+projects.map(p=>`<button type="button" class="sidebar-project ${p.name===environmentProject?'active':''}" data-select-project="${escape(p.name)}">${escape(p.name)}</button>`).join('')+(inventoryImported?'<p class="sidebar-label">Workflows</p>'+environmentWorkflows.
|
|
424
|
+
target.innerHTML='<p class="sidebar-label">Repositories</p>'+projects.map(p=>`<button type="button" class="sidebar-project ${p.name===environmentProject?'active':''}" data-select-project="${escape(p.name)}">${escape(p.name)}</button>`).join('')+(inventoryImported?'<p class="sidebar-label">Workflows · '+environmentWorkflows.length+'</p>'+['Email','Calendar','Files','API routes','Other workflows'].map(group=>{const items=sortedWorkflows(environmentWorkflows).filter(w=>workflowGroup(w)===group);return items.length?`<details class="sidebar-workflow-group" ${group==='Email'?'open':''}><summary>${escape(group)} <span>${items.length}</span></summary>${items.map(w=>`<button type="button" class="sidebar-workflow" title="${escape(w.entrypoint??w.id)}" data-sidebar-workflow="${escape(w.id)}">${escape(workflowName(w))}</button>`).join('')}</details>`:'';}).join(''):'<p class="sidebar-empty">Synced repositories appear here.</p>');
|
|
412
425
|
}
|
|
413
426
|
addEventListener('agenttwin:projects',renderProjectSidebar);
|
|
414
427
|
addEventListener('agenttwin:discovery',renderProjectSidebar);
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
<meta name="description" content="AgentTwin workspace: workflow tests, observed activities, Git revisions and failed checks — all from recorded evidence.">
|
|
7
7
|
<meta name="theme-color" content="#101013">
|
|
8
8
|
<link rel="icon" href="assets/logo/favicon.svg"><link rel="stylesheet" href="assets/brand.css"><link rel="stylesheet" href="assets/dashboard/dashboard.css"><link rel="stylesheet" href="assets/dashboard/workspace.css?v=0.10.0"><link rel="stylesheet" href="assets/dashboard/agent-home.css?v=0.10.0">
|
|
9
|
-
<script src="assets/dashboard/dashboard.js?v=0.10.
|
|
9
|
+
<script src="assets/dashboard/dashboard.js?v=0.10.2" type="module"></script><script src="assets/vendor/marked.js" defer></script>
|
|
10
10
|
<script src="assets/vendor/purify.min.js" defer></script>
|
|
11
|
-
<script src="assets/dashboard/agent-home.js?v=0.10.
|
|
11
|
+
<script src="assets/dashboard/agent-home.js?v=0.10.1" type="module"></script><script src="assets/dashboard/hosted.js?v=0.4.0" defer></script><script src="assets/dashboard/workflows.js?v=0.2.0" type="module"></script>
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<a class="skip" href="#main">Skip to content</a>
|