@tuent/sentinel 0.1.2 → 0.1.4

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/cli.js CHANGED
@@ -1,34 +1,34 @@
1
1
  #!/usr/bin/env node
2
- import{runInitClaudeCode as de}from"./chunk-FWIISAZZ.js";import{AgentProfileManager as H,AlertManager as ge,AuditTrail as A,BaselineBuilder as N,CorrelationDetector as ue,DeviationDetector as B,FileStorageBackend as fe,ProfileStore as pe,ReportGenerator as me,Sentinel as T,SentinelRunner as he,generateFleetReport as ye}from"./chunk-GRN5P3H2.js";import{readReleaseToken as we}from"./chunk-LATQNIRW.js";import{deriveAgentId as $e}from"./chunk-B5QKJHSV.js";import"./chunk-FMZWHT4M.js";import"./chunk-QIYQWOLO.js";import{loadPolicy as ve}from"./chunk-WLIDSTS4.js";import{getOrCreateKeyPair as E}from"./chunk-NUXSUSYY.js";import{join as u}from"path";import{homedir as m}from"os";import{readFile as M,writeFile as R,access as K,mkdir as W}from"fs/promises";function Ae(e){const o=new Date(e);if(isNaN(o.getTime()))return"unknown";const t=Date.now()-o.getTime();if(t<0)return"just now";const a=Math.floor(t/6e4);if(a<1)return"just now";if(a<60)return`${a} minute${a===1?"":"s"} ago`;const r=Math.floor(a/60);if(r<24)return`${r} hour${r===1?"":"s"} ago`;const i=Math.floor(r/24);if(i<14)return`${i} day${i===1?"":"s"} ago`;const d=Math.floor(i/7);if(i<60)return`${d} week${d===1?"":"s"} ago`;if(i>=365)return"over a year ago";const c=Math.floor(i/30);return`${c} month${c===1?"":"s"} ago`}function Ie(e,o){const n=new Date(o),t=isNaN(n.getTime())?1/0:Math.floor((Date.now()-n.getTime())/864e5);return e<.3||t>30?"declining":e>.7&&t<7?"rising":"stable"}function Se(e){return e<.3?"inner":e<.65?"middle":"outer"}function G(e){if(e.length===0)return"No petals selected.";const o=new Map;for(const t of e)o.set(t.id,t.label);const n=[`Selected petals (${e.length}):
3
- `];for(const t of e){const a=Se(t.layer),r=t.isRichData?"":" [filler]";n.push(`- ${t.label}${r}`),n.push(` Category: ${t.category}`),n.push(` Layer zone: ${a} (${(t.layer*100).toFixed(0)}%)`),n.push(` Openness: ${(t.openness*100).toFixed(0)}%`),n.push(` Description: ${t.description}`),n.push(` Last active: ${t.lastActive}`);const i=Ae(t.lastActive),d=t.weight!=null?Ie(t.weight,t.lastActive):"stable";if(n.push(` Temporal: Last active ${i} | Weight trend: ${d}`),t.source){const c={seed:"seed data",agent:"observed from activity",manual:"filesystem scan",diary:"personal diary entry",conversation:"created from conversation","agent-monitor":"monitored agent activity"};n.push(` Source: ${c[t.source]??t.source}`)}if(t.weight!=null&&n.push(` Weight: ${t.weight.toFixed(2)}`),t.connections.length>0){const c=t.connections.map(s=>o.get(s)??De(s));n.push(` Connections: ${c.join(", ")}`)}if(t.files&&t.files.length>0){const c=t.files.slice(0,10).map(s=>s.split("/").pop()??s);n.push(` Key files: ${c.join(", ")}`)}if(t.fileContents&&t.fileContents.length>0){n.push(" File contents:");for(const c of t.fileContents)n.push(` --- ${c.name} ---`),n.push(c.content.split(`
4
- `).map(s=>` ${s}`).join(`
2
+ import"./chunk-TKAKHSZ3.js";import{AgentProfileManager as B,AlertManager as me,AuditTrail as b,BaselineBuilder as x,CorrelationDetector as he,DeviationDetector as _,FileStorageBackend as ye,ProfileStore as we,ReportGenerator as ve,Sentinel as E,SentinelRunner as $e,generateFleetReport as Ae}from"./chunk-I2FVDDSG.js";import{runInitClaudeCode as be}from"./chunk-LTBVWF5H.js";import{readReleaseToken as Ie}from"./chunk-LATQNIRW.js";import{deriveAgentId as Se}from"./chunk-B5QKJHSV.js";import"./chunk-B6S2PBS4.js";import"./chunk-FIEIGBYL.js";import{checkSaneNumber as P,loadPolicy as De,suggestKey as Te}from"./chunk-KWZ7JKKO.js";import{getOrCreateKeyPair as R}from"./chunk-NUXSUSYY.js";import{join as g}from"path";import{homedir as h}from"os";import{readFile as N,writeFile as C,access as G,mkdir as U}from"fs/promises";function ke(t){const o=new Date(t);if(isNaN(o.getTime()))return"unknown";const e=Date.now()-o.getTime();if(e<0)return"just now";const s=Math.floor(e/6e4);if(s<1)return"just now";if(s<60)return`${s} minute${s===1?"":"s"} ago`;const i=Math.floor(s/60);if(i<24)return`${i} hour${i===1?"":"s"} ago`;const a=Math.floor(i/24);if(a<14)return`${a} day${a===1?"":"s"} ago`;const d=Math.floor(a/7);if(a<60)return`${d} week${d===1?"":"s"} ago`;if(a>=365)return"over a year ago";const c=Math.floor(a/30);return`${c} month${c===1?"":"s"} ago`}function Ee(t,o){const n=new Date(o),e=isNaN(n.getTime())?1/0:Math.floor((Date.now()-n.getTime())/864e5);return t<.3||e>30?"declining":t>.7&&e<7?"rising":"stable"}function Ce(t){return t<.3?"inner":t<.65?"middle":"outer"}function J(t){if(t.length===0)return"No petals selected.";const o=new Map;for(const e of t)o.set(e.id,e.label);const n=[`Selected petals (${t.length}):
3
+ `];for(const e of t){const s=Ce(e.layer),i=e.isRichData?"":" [filler]";n.push(`- ${e.label}${i}`),n.push(` Category: ${e.category}`),n.push(` Layer zone: ${s} (${(e.layer*100).toFixed(0)}%)`),n.push(` Openness: ${(e.openness*100).toFixed(0)}%`),n.push(` Description: ${e.description}`),n.push(` Last active: ${e.lastActive}`);const a=ke(e.lastActive),d=e.weight!=null?Ee(e.weight,e.lastActive):"stable";if(n.push(` Temporal: Last active ${a} | Weight trend: ${d}`),e.source){const c={seed:"seed data",agent:"observed from activity",manual:"filesystem scan",diary:"personal diary entry",conversation:"created from conversation","agent-monitor":"monitored agent activity"};n.push(` Source: ${c[e.source]??e.source}`)}if(e.weight!=null&&n.push(` Weight: ${e.weight.toFixed(2)}`),e.connections.length>0){const c=e.connections.map(r=>o.get(r)??je(r));n.push(` Connections: ${c.join(", ")}`)}if(e.files&&e.files.length>0){const c=e.files.slice(0,10).map(r=>r.split("/").pop()??r);n.push(` Key files: ${c.join(", ")}`)}if(e.fileContents&&e.fileContents.length>0){n.push(" File contents:");for(const c of e.fileContents)n.push(` --- ${c.name} ---`),n.push(c.content.split(`
4
+ `).map(r=>` ${r}`).join(`
5
5
  `))}n.push("")}return n.join(`
6
- `)}function De(e){return e.split("-").map(o=>o.charAt(0).toUpperCase()+o.slice(1)).join(" ")}function be(e){const o=[Te(e.agentName,e.agentId,e.role),Re(e.baseline),Ce(e.e),Ee(e.findings),Me()],n=je(e.t);return n&&o.push(n),o.join(`
6
+ `)}function je(t){return t.split("-").map(o=>o.charAt(0).toUpperCase()+o.slice(1)).join(" ")}function Me(t){const o=[Re(t.agentName,t.agentId,t.role),Ne(t.baseline),Fe(t.e),xe(t.findings),Pe()],n=Le(t.t);return n&&o.push(n),o.join(`
7
7
 
8
- `)}function Te(e,o,n){const t=["=== Agent Identity ===",`Name: ${e}`,`ID: ${o}`];if(n){if(t.push(`Role: ${n.description}`),t.push(`Allowed actions: ${n.allowedActions.length>0?n.allowedActions.join(", "):"Not defined"}`),n.forbiddenTargetPatterns.length>0&&t.push(`Forbidden targets: ${n.forbiddenTargetPatterns.join(", ")}`),n.expectedSchedule){const a=[];n.expectedSchedule.activeHours&&a.push(`hours ${n.expectedSchedule.activeHours[0]}-${n.expectedSchedule.activeHours[1]}`),n.expectedSchedule.activeDays&&a.push(`days ${n.expectedSchedule.activeDays.join(", ")}`),a.length>0&&t.push(`Expected schedule: ${a.join("; ")}`)}}else t.push("No role definition \u2014 monitoring behavioral baseline only");return t.join(`
9
- `)}function Re(e){if(!e)return`=== Behavioral Baseline ===
10
- Not yet established. Insufficient data for baseline computation.`;const o=Object.entries(e.actionDistribution).map(([t,a])=>`${t}: ${a}%`).join(", ");return["=== Behavioral Baseline ===",`Period: ${e.periodDays} days`,`Total sessions: ${e.totalSessions}`,`Total events: ${e.totalEvents}`,`Average events per session: ${e.averageEventsPerSession}`,`Average session duration: ${e.averageSessionDurationMinutes} minutes`,`Typical active hours: ${e.typicalActiveHours[0]}-${e.typicalActiveHours[1]}`,`Typical active days: ${e.typicalActiveDays.length>0?e.typicalActiveDays.join(", "):"N/A"}`,`Action distribution: ${o||"N/A"}`,`Normal weight range: ${e.normalWeightRange[0].toFixed(2)}-${e.normalWeightRange[1].toFixed(2)}`,`Top targets: ${e.topTargets.length>0?e.topTargets.slice(0,10).join(", "):"N/A"}`].join(`
11
- `)}function Ce(e){const o=`=== Recent Activity (${e.length} sessions) ===`;return e.length===0?`${o}
8
+ `)}function Re(t,o,n){const e=["=== Agent Identity ===",`Name: ${t}`,`ID: ${o}`];if(n){if(e.push(`Role: ${n.description}`),e.push(`Allowed actions: ${n.allowedActions.length>0?n.allowedActions.join(", "):"Not defined"}`),n.forbiddenTargetPatterns.length>0&&e.push(`Forbidden targets: ${n.forbiddenTargetPatterns.join(", ")}`),n.expectedSchedule){const s=[];n.expectedSchedule.activeHours&&s.push(`hours ${n.expectedSchedule.activeHours[0]}-${n.expectedSchedule.activeHours[1]}`),n.expectedSchedule.activeDays&&s.push(`days ${n.expectedSchedule.activeDays.join(", ")}`),s.length>0&&e.push(`Expected schedule: ${s.join("; ")}`)}}else e.push("No role definition \u2014 monitoring behavioral baseline only");return e.join(`
9
+ `)}function Ne(t){if(!t)return`=== Behavioral Baseline ===
10
+ Not yet established. Insufficient data for baseline computation.`;const o=Object.entries(t.actionDistribution).map(([e,s])=>`${e}: ${s}%`).join(", ");return["=== Behavioral Baseline ===",`Period: ${t.periodDays} days`,`Total sessions: ${t.totalSessions}`,`Total events: ${t.totalEvents}`,`Average events per session: ${t.averageEventsPerSession}`,`Average session duration: ${t.averageSessionDurationMinutes} minutes`,`Typical active hours: ${t.typicalActiveHours[0]}-${t.typicalActiveHours[1]}`,`Typical active days: ${t.typicalActiveDays.length>0?t.typicalActiveDays.join(", "):"N/A"}`,`Action distribution: ${o||"N/A"}`,`Normal weight range: ${t.normalWeightRange[0].toFixed(2)}-${t.normalWeightRange[1].toFixed(2)}`,`Top targets: ${t.topTargets.length>0?t.topTargets.slice(0,10).join(", "):"N/A"}`].join(`
11
+ `)}function Fe(t){const o=`=== Recent Activity (${t.length} sessions) ===`;return t.length===0?`${o}
12
12
  No recent activity recorded.`:`${o}
13
- ${G(e)}`}function Ee(e){const o="=== Security Findings ===";if(e.length===0)return`${o}
14
- No security findings detected. Agent behavior is within expected parameters.`;const n=[o];for(const t of e)n.push(`[${t.severity}] ${t.type}`),n.push(` Description: ${t.description}`),n.push(` Evidence: ${t.evidence.action} \u2192 ${t.evidence.target} at ${t.evidence.timestamp}`),t.evidence.baselineComparison&&n.push(` Baseline comparison: ${t.evidence.baselineComparison}`),n.push(` Recommendation: ${t.recommendation}`),n.push("");return n.join(`
15
- `)}function Me(){return["=== Analysis Request ===","Based on the data above, provide:","1. OVERALL HEALTH ASSESSMENT: Is this agent operating normally?","2. RISK SUMMARY: What is the overall risk level?","3. FINDING REVIEW: For each finding, confirm or adjust its severity with reasoning.","4. RECOMMENDATIONS: Specific actions the owner should take.","5. MONITORING GUIDANCE: What patterns to watch for going forward."].join(`
16
- `)}function je(e){return!e||e.length===0?null:`=== Owner Context ===
17
- ${G(e)}`}import{request as J}from"http";var f=process.argv.slice(2),p=w(f,"--agent"),U=w(f,"--name"),ke=w(f,"--role"),Ne=f.includes("--create"),Fe=f.includes("--compute-baseline"),xe=f.includes("--monitor"),Pe=f.includes("--init-config"),Oe=f.includes("--init-policy"),Le=f.includes("init")&&f.includes("claude-code"),qe=f.includes("--force"),_=w(f,"--from-policy"),He=f.includes("--report"),Be=f.includes("--report-all"),Ke=f.includes("--correlations"),We=f.includes("--verify-audit"),Ge=f.includes("--enroll-manifest"),Je=f.includes("--recompute-stats"),Ue=f.includes("--health"),_e=f.includes("--restrict"),Qe=f.includes("--quarantine"),Ve=f.includes("--release")||f[0]==="release",ze=f.includes("--status"),Ye=f.includes("--start-task"),Xe=f.includes("--end-task"),Ze=f.includes("--intent-status"),et=f.includes("--intent-report"),Q=w(f,"--task-id"),V=w(f,"--description"),z=w(f,"--relaxed-actions"),Y=w(f,"--phases"),X=w(f,"--reason"),F=w(f,"--quarantine"),x=w(f,"--quarantine-reason"),j=w(f,"--period"),Z=w(f,"--format"),S=new H;function w(e,o){const n=e.find(a=>a.startsWith(o+"="));if(!n)return;const t=n.indexOf("=");return n.slice(t+1)}async function b(e,o){try{const n=await M(u(e,o,"mode.json"),"utf-8"),t=JSON.parse(n);return t.mode==="restricted"||t.mode==="quarantined"||t.mode==="normal"?t:{mode:"normal"}}catch{return{mode:"normal"}}}function ee(e){const o=["Agent","Score","Status","Mode","C/H/M/L","Last Event","Baseline"],n=e.map(s=>[s.agentId,String(s.score),s.status,s.mode,s.findings,s.lastEvent,s.baseline?"\u2713":"\u2717"]),t=[o,...n],a=o.map((s,l)=>Math.max(...t.map(g=>g[l].length))),r=(s,l)=>s+" ".repeat(l-s.length),i=o.map((s,l)=>r(s,a[l])).join(" "),d=a.map(s=>"-".repeat(s)).join(" "),c=n.map(s=>s.map((l,g)=>r(l,a[g])).join(" "));return[i,d,...c].join(`
18
- `)}function te(e){const o=["Agent ID","Mode","Reason","Changed"],n=e.map(s=>[s.agentId,s.mode.toUpperCase(),s.reason,s.changed]),t=[o,...n],a=o.map((s,l)=>Math.max(...t.map(g=>g[l].length))),r=(s,l)=>s+" ".repeat(l-s.length),i=o.map((s,l)=>r(s,a[l])).join(" "),d=a.map(s=>"-".repeat(s)).join(" "),c=n.map(s=>s.map((l,g)=>r(l,a[g])).join(" "));return[i,d,...c].join(`
19
- `)}function ne(e){const o=["Agent ID","Sessions","Last Active","Role","Baseline","Mode","Status"],n=e.map(s=>[s.agentId,String(s.sessions),s.lastActive,s.roleDefined?"\u2713":"\u2717",s.baselineDefined?"\u2713":"\u2717",s.mode??"normal",s.status]),t=[o,...n],a=o.map((s,l)=>Math.max(...t.map(g=>g[l].length))),r=(s,l)=>s+" ".repeat(l-s.length),i=o.map((s,l)=>r(s,a[l])).join(" "),d=a.map(s=>"-".repeat(s)).join(" "),c=n.map(s=>s.map((l,g)=>r(l,a[g])).join(" "));return[i,d,...c].join(`
20
- `)}function P(e,o){return e<10?"New":o.some(n=>n.severity==="HIGH"||n.severity==="CRITICAL")?"At Risk":o.some(n=>n.severity==="MEDIUM")?"Caution":"Healthy"}function O(e,o){const n=Date.now()-o*24*60*60*1e3;return e.filter(t=>new Date(t.lastActive).getTime()>=n)}function oe(e){return{id:e.id,label:e.label,category:e.category,description:e.description,layer:e.layer,lastActive:e.lastActive,openness:e.openness,connections:e.connections,isRichData:!0,source:e.source,core:e.core,sharable:e.sharable,weight:e.weight,files:e.files}}function L(e,o){const n=["Sentinel \u2014 Live Monitoring","\u2500".repeat(26)];for(const[t,a]of e){const r=(o.get(t)??"manual").toUpperCase(),i=a.getFindings(),d=i.filter(y=>y.severity==="HIGH"||y.severity==="CRITICAL").length,c=i.filter(y=>y.severity==="MEDIUM").length;let s=`${i.length} findings`;d>0?s=`${d} HIGH`:c>0&&(s=`${c} MEDIUM`);const l=P(a.sessionCount,i),g=t.padEnd(18),h=r.padEnd(5);n.push(`[${g}] ${h}| ${a.eventCount} events | ${a.sessionCount} sessions | ${s} | ${l}`)}return n.push(""),n.push("Press Ctrl+C to stop."),n.join(`
21
- `)}function ae(e){const o=["Monitoring stopped. Summary:"];for(const[n,t]of e){const a=t.getFindings();let r=`${a.length} findings`;if(a.length>0){const i={};for(const c of a)i[c.severity]=(i[c.severity]??0)+1;const d=Object.entries(i).map(([c,s])=>`${s} ${c}`);r=`${a.length} finding${a.length>1?"s":""} (${d.join(", ")})`}o.push(`- ${n}: ${t.eventCount} events, ${t.sessionCount} sessions, ${r}`)}return o.join(`
22
- `)}async function tt(e,o){const n=u(m(),".dahlia","agents"),t=await b(n,e);if(t.mode==="quarantined"){console.log(`Agent ${e} is quarantined \u2014 cannot downgrade to restricted.`);return}const a=t.mode;await W(u(n,e),{recursive:!0}),await R(u(n,e,"mode.json"),JSON.stringify({mode:"restricted",reason:o,timestamp:new Date().toISOString(),previousMode:a}),"utf-8");const r=await E(u(n,e)),i=new A(e,{logDir:u(n,e)});await i.open(),i.setSigningKey(r.privateKey,r.publicKey),await i.logModeChange("restricted",o,a),await i.close(),console.log(`Agent ${e} RESTRICTED: ${o}`)}async function nt(e,o){const n=u(m(),".dahlia","agents"),a=(await b(n,e)).mode;await W(u(n,e),{recursive:!0}),await R(u(n,e,"mode.json"),JSON.stringify({mode:"quarantined",reason:o,timestamp:new Date().toISOString(),previousMode:a}),"utf-8");const r=await E(u(n,e)),i=new A(e,{logDir:u(n,e)});await i.open(),i.setSigningKey(r.privateKey,r.publicKey),await i.logModeChange("quarantined",o,a),await i.close(),console.log(`Agent ${e} QUARANTINED: ${o}`)}function ot(e,o=1500){return new Promise(n=>{const t=J({host:"127.0.0.1",port:e,path:"/api/sentinel/health",method:"GET"},a=>{a.resume(),n(a.statusCode===200)});t.on("error",()=>n(!1)),t.setTimeout(o,()=>{t.destroy(),n(!1)}),t.end()})}function at(e,o,n){return new Promise((t,a)=>{const r=JSON.stringify({agentId:n,reason:"operator release (sentinel release)"}),i=J({host:"127.0.0.1",port:e,path:"/api/sentinel/release",method:"POST",headers:{"content-type":"application/json","content-length":Buffer.byteLength(r),"x-sentinel-token":o}},d=>{let c="";d.on("data",s=>c+=s),d.on("end",()=>{try{t({status:d.statusCode??0,body:JSON.parse(c)})}catch{t({status:d.statusCode??0,body:c})}})});i.on("error",a),i.setTimeout(3e3,()=>i.destroy(new Error("timeout"))),i.write(r),i.end()})}function se(e){const{id:o,selfDerived:n,previousMode:t,mode:a}=e;return n&&t==="normal"?`Agent ${o} was already normal \u2014 no change. If you are still locked out, your workspace id may differ (e.g. a symlinked root). Run: sentinel release --agent=<id>`:`[live] Agent ${o} RELEASED \u2014 was ${t}, now ${a}. Applied to the running daemon (no restart).`}async function st(e){const o=u(m(),".dahlia","agents"),n=e===void 0,t=e??$e(process.cwd());n&&console.log(`Resolved agent-id from cwd: ${t}`);const a=we(m());if(a&&await ot(a.port))try{const s=await at(a.port,a.token,t);if(s.status===200&&typeof s.body=="object"&&s.body.ok){console.log(se({id:t,selfDerived:n,previousMode:String(s.body.previousMode),mode:String(s.body.mode)}));return}const l=typeof s.body=="object"?JSON.stringify(s.body):s.body;console.error(`[live] daemon refused release (HTTP ${s.status}): ${l}`);return}catch(s){console.error(`[live] daemon release call failed: ${s.message}. Falling back to mode.json.`)}const r=await b(o,t);if(r.mode==="normal"){console.log(`Agent ${t} is already in normal mode.`);return}const i=r.mode;await R(u(o,t,"mode.json"),JSON.stringify({mode:"normal",reason:"manual release",timestamp:new Date().toISOString(),previousMode:i}),"utf-8");const d=await E(u(o,t)),c=new A(t,{logDir:u(o,t)});await c.open(),c.setSigningKey(d.privateKey,d.publicKey),await c.logModeChange("normal","manual release",i),await c.close(),console.log(`[fallback] Agent ${t} RELEASED (was ${i}) \u2014 no running daemon; mode.json written, applies on the daemon's next start.`)}async function it(){const e=await S.listAgents();if(e.length===0){console.log("No monitored agents found.");return}const o=u(m(),".dahlia","agents"),n=[];for(const a of e){const r=await b(o,a);n.push({agentId:a,mode:r.mode,reason:r.reason??"-",changed:r.timestamp?r.timestamp.split("T")[0]:"-"})}console.log(`Agent Mode Status
23
- `),console.log(te(n));const t=n.filter(a=>a.mode!=="normal");if(t.length>0){console.log("");for(const a of t){const r=a.mode==="quarantined"?"QUARANTINED":"RESTRICTED";console.log(`\u26A0 ${a.agentId}: ${r}`)}}}async function rt(){const e=await S.listAgents();if(e.length===0){console.log("No monitored agents found. Create one with --create --agent=ID --name=NAME");return}const o=[];for(const t of e)try{const i=(await S.loadAgentProfile(t)).build().filter(I=>I.source==="agent-monitor"),d=i.length,c=i.length>0?i.map(I=>I.lastActive).sort().reverse()[0].split("T")[0]:"never",l=await new N(t).loadBaseline(t),g=await S.loadRole(t),h=[];if(l){const I=O(i,7),k=new B(l,g);for(const $ of I){const D={label:$.label,category:$.category,lastActive:$.lastActive,description:$.description,weight:$.weight,source:$.source,files:$.files,connections:$.connections};h.push(...k.analyzeSession(D))}}const y=u(m(),".dahlia","agents"),C=await b(y,t);o.push({agentId:t,sessions:d,lastActive:c,roleDefined:g!==null,baselineDefined:l!==null,status:P(d,h),mode:C.mode})}catch(a){console.warn(`Error loading agent ${t}:`,a),o.push({agentId:t,sessions:0,lastActive:"error",roleDefined:!1,baselineDefined:!1,status:"Error",mode:"normal"})}const n=o.filter(t=>t.mode&&t.mode!=="normal");if(n.length>0){for(const t of n){const a=t.mode==="quarantined"?"QUARANTINED":"RESTRICTED";console.log(`\u26A0 ${t.agentId}: ${a}`)}console.log("")}console.log(`Sentinel Agent Overview
24
- `),console.log(ne(o))}async function lt(e){const o=await S.loadAgentProfile(e),n=await S.loadRole(e),a=await new N(e).loadBaseline(e),i=o.build().filter(D=>D.source==="agent-monitor"),d=O(i,7),c=d.map(oe),s=[];if(a){const D=new B(a,n);for(const v of d){const ce={label:v.label,category:v.category,lastActive:v.lastActive,description:v.description,weight:v.weight,source:v.source,files:v.files,connections:v.connections};s.push(...D.analyzeSession(ce))}}const l=u(m(),".dahlia","profile.json"),g=new fe(l),h=new pe({backend:g});let y=[];await h.load()&&(y=h.build().filter(v=>v.core===!0).map(oe));const I=be({agentId:e,agentName:e,role:n,baseline:a,e:c,findings:s,t:y}),k=new Date().toISOString().split("T")[0],$=`Sentinel Agent Report \u2014 ${e} \u2014 ${d.length} recent sessions \u2014 generated ${k}
13
+ ${J(t)}`}function xe(t){const o="=== Security Findings ===";if(t.length===0)return`${o}
14
+ No security findings detected. Agent behavior is within expected parameters.`;const n=[o];for(const e of t)n.push(`[${e.severity}] ${e.type}`),n.push(` Description: ${e.description}`),n.push(` Evidence: ${e.evidence.action} \u2192 ${e.evidence.target} at ${e.evidence.timestamp}`),e.evidence.baselineComparison&&n.push(` Baseline comparison: ${e.evidence.baselineComparison}`),n.push(` Recommendation: ${e.recommendation}`),n.push("");return n.join(`
15
+ `)}function Pe(){return["=== Analysis Request ===","Based on the data above, provide:","1. OVERALL HEALTH ASSESSMENT: Is this agent operating normally?","2. RISK SUMMARY: What is the overall risk level?","3. FINDING REVIEW: For each finding, confirm or adjust its severity with reasoning.","4. RECOMMENDATIONS: Specific actions the owner should take.","5. MONITORING GUIDANCE: What patterns to watch for going forward."].join(`
16
+ `)}function Le(t){return!t||t.length===0?null:`=== Owner Context ===
17
+ ${J(t)}`}import{request as V}from"http";var u=process.argv.slice(2),p=v(u,"--agent"),Q=v(u,"--name"),qe=v(u,"--role"),Oe=u.includes("--create"),He=u.includes("--compute-baseline"),Ke=u.includes("--monitor"),We=u.includes("--init-config"),Be=u.includes("--init-policy"),_e=u.includes("init")&&u.includes("claude-code"),Ge=u.includes("--force"),z=v(u,"--from-policy"),Ue=u.includes("--report"),Je=u.includes("--report-all"),Ve=u.includes("--correlations"),Qe=u.includes("--verify-audit"),ze=u.includes("--enroll-manifest"),Ye=u.includes("--recompute-stats"),Xe=u.includes("--health"),Ze=u.includes("--restrict"),et=u.includes("--quarantine"),tt=u.includes("--release")||u[0]==="release",nt=u.includes("--status"),ot=u.includes("--start-task"),st=u.includes("--end-task"),at=u.includes("--intent-status"),it=u.includes("--intent-report"),Y=v(u,"--task-id"),X=v(u,"--description"),Z=v(u,"--relaxed-actions"),ee=v(u,"--phases"),te=v(u,"--reason"),L=v(u,"--quarantine"),q=v(u,"--quarantine-reason"),T=v(u,"--period"),ne=v(u,"--format"),S=new B;function v(t,o){const n=t.find(s=>s.startsWith(o+"="));if(!n)return;const e=n.indexOf("=");return n.slice(e+1)}async function k(t,o){try{const n=await N(g(t,o,"mode.json"),"utf-8"),e=JSON.parse(n);return e.mode==="restricted"||e.mode==="quarantined"||e.mode==="normal"?e:{mode:"normal"}}catch{return{mode:"normal"}}}function oe(t){const o=["Agent","Score","Status","Mode","C/H/M/L","Last Event","Baseline"],n=t.map(r=>[r.agentId,String(r.score),r.status,r.mode,r.findings,r.lastEvent,r.baseline?"\u2713":"\u2717"]),e=[o,...n],s=o.map((r,l)=>Math.max(...e.map(f=>f[l].length))),i=(r,l)=>r+" ".repeat(l-r.length),a=o.map((r,l)=>i(r,s[l])).join(" "),d=s.map(r=>"-".repeat(r)).join(" "),c=n.map(r=>r.map((l,f)=>i(l,s[f])).join(" "));return[a,d,...c].join(`
18
+ `)}function se(t){const o=["Agent ID","Mode","Reason","Changed"],n=t.map(r=>[r.agentId,r.mode.toUpperCase(),r.reason,r.changed]),e=[o,...n],s=o.map((r,l)=>Math.max(...e.map(f=>f[l].length))),i=(r,l)=>r+" ".repeat(l-r.length),a=o.map((r,l)=>i(r,s[l])).join(" "),d=s.map(r=>"-".repeat(r)).join(" "),c=n.map(r=>r.map((l,f)=>i(l,s[f])).join(" "));return[a,d,...c].join(`
19
+ `)}function ae(t){const o=["Agent ID","Sessions","Last Active","Role","Baseline","Mode","Status"],n=t.map(r=>[r.agentId,String(r.sessions),r.lastActive,r.roleDefined?"\u2713":"\u2717",r.baselineDefined?"\u2713":"\u2717",r.mode??"normal",r.status]),e=[o,...n],s=o.map((r,l)=>Math.max(...e.map(f=>f[l].length))),i=(r,l)=>r+" ".repeat(l-r.length),a=o.map((r,l)=>i(r,s[l])).join(" "),d=s.map(r=>"-".repeat(r)).join(" "),c=n.map(r=>r.map((l,f)=>i(l,s[f])).join(" "));return[a,d,...c].join(`
20
+ `)}function O(t,o){return t<10?"New":o.some(n=>n.severity==="HIGH"||n.severity==="CRITICAL")?"At Risk":o.some(n=>n.severity==="MEDIUM")?"Caution":"Healthy"}function H(t,o){const n=Date.now()-o*24*60*60*1e3;return t.filter(e=>new Date(e.lastActive).getTime()>=n)}function ie(t){return{id:t.id,label:t.label,category:t.category,description:t.description,layer:t.layer,lastActive:t.lastActive,openness:t.openness,connections:t.connections,isRichData:!0,source:t.source,core:t.core,sharable:t.sharable,weight:t.weight,files:t.files}}function K(t,o){const n=["Sentinel \u2014 Live Monitoring","\u2500".repeat(26)];for(const[e,s]of t){const i=(o.get(e)??"manual").toUpperCase(),a=s.getFindings(),d=a.filter(w=>w.severity==="HIGH"||w.severity==="CRITICAL").length,c=a.filter(w=>w.severity==="MEDIUM").length;let r=`${a.length} findings`;d>0?r=`${d} HIGH`:c>0&&(r=`${c} MEDIUM`);const l=O(s.sessionCount,a),f=e.padEnd(18),y=i.padEnd(5);n.push(`[${f}] ${y}| ${s.eventCount} events | ${s.sessionCount} sessions | ${r} | ${l}`)}return n.push(""),n.push("Press Ctrl+C to stop."),n.join(`
21
+ `)}function re(t){const o=["Monitoring stopped. Summary:"];for(const[n,e]of t){const s=e.getFindings();let i=`${s.length} findings`;if(s.length>0){const a={};for(const c of s)a[c.severity]=(a[c.severity]??0)+1;const d=Object.entries(a).map(([c,r])=>`${r} ${c}`);i=`${s.length} finding${s.length>1?"s":""} (${d.join(", ")})`}o.push(`- ${n}: ${e.eventCount} events, ${e.sessionCount} sessions, ${i}`)}return o.join(`
22
+ `)}async function rt(t,o){const n=g(h(),".dahlia","agents"),e=await k(n,t);if(e.mode==="quarantined"){console.log(`Agent ${t} is quarantined \u2014 cannot downgrade to restricted.`);return}const s=e.mode;await U(g(n,t),{recursive:!0}),await C(g(n,t,"mode.json"),JSON.stringify({mode:"restricted",reason:o,timestamp:new Date().toISOString(),previousMode:s}),"utf-8");const i=await R(g(n,t)),a=new b(t,{logDir:g(n,t)});await a.open(),a.setSigningKey(i.privateKey,i.publicKey),await a.logModeChange("restricted",o,s),await a.close(),console.log(`Agent ${t} RESTRICTED: ${o}`)}async function lt(t,o){const n=g(h(),".dahlia","agents"),s=(await k(n,t)).mode;await U(g(n,t),{recursive:!0}),await C(g(n,t,"mode.json"),JSON.stringify({mode:"quarantined",reason:o,timestamp:new Date().toISOString(),previousMode:s}),"utf-8");const i=await R(g(n,t)),a=new b(t,{logDir:g(n,t)});await a.open(),a.setSigningKey(i.privateKey,i.publicKey),await a.logModeChange("quarantined",o,s),await a.close(),console.log(`Agent ${t} QUARANTINED: ${o}`)}function ct(t,o=1500){return new Promise(n=>{const e=V({host:"127.0.0.1",port:t,path:"/api/sentinel/health",method:"GET"},s=>{s.resume(),n(s.statusCode===200)});e.on("error",()=>n(!1)),e.setTimeout(o,()=>{e.destroy(),n(!1)}),e.end()})}function dt(t,o,n){return new Promise((e,s)=>{const i=JSON.stringify({agentId:n,reason:"operator release (sentinel release)"}),a=V({host:"127.0.0.1",port:t,path:"/api/sentinel/release",method:"POST",headers:{"content-type":"application/json","content-length":Buffer.byteLength(i),"x-sentinel-token":o}},d=>{let c="";d.on("data",r=>c+=r),d.on("end",()=>{try{e({status:d.statusCode??0,body:JSON.parse(c)})}catch{e({status:d.statusCode??0,body:c})}})});a.on("error",s),a.setTimeout(3e3,()=>a.destroy(new Error("timeout"))),a.write(i),a.end()})}function le(t){const{id:o,selfDerived:n,previousMode:e,mode:s}=t;return n&&e==="normal"?`Agent ${o} was already normal \u2014 no change. If you are still locked out, your workspace id may differ (e.g. a symlinked root). Run: sentinel release --agent=<id>`:`[live] Agent ${o} RELEASED \u2014 was ${e}, now ${s}. Applied to the running daemon (no restart).`}async function gt(t){const o=g(h(),".dahlia","agents"),n=t===void 0,e=t??Se(process.cwd());n&&console.log(`Resolved agent-id from cwd: ${e}`);const s=Ie(h());if(s&&await ct(s.port))try{const r=await dt(s.port,s.token,e);if(r.status===200&&typeof r.body=="object"&&r.body.ok){console.log(le({id:e,selfDerived:n,previousMode:String(r.body.previousMode),mode:String(r.body.mode)}));return}const l=typeof r.body=="object"?JSON.stringify(r.body):r.body;console.error(`[live] daemon refused release (HTTP ${r.status}): ${l}`);return}catch(r){console.error(`[live] daemon release call failed: ${r.message}. Falling back to mode.json.`)}const i=await k(o,e);if(i.mode==="normal"){console.log(`Agent ${e} is already in normal mode.`);return}const a=i.mode;await C(g(o,e,"mode.json"),JSON.stringify({mode:"normal",reason:"manual release",timestamp:new Date().toISOString(),previousMode:a}),"utf-8");const d=await R(g(o,e)),c=new b(e,{logDir:g(o,e)});await c.open(),c.setSigningKey(d.privateKey,d.publicKey),await c.logModeChange("normal","manual release",a),await c.close(),console.log(`[fallback] Agent ${e} RELEASED (was ${a}) \u2014 no running daemon; mode.json written, applies on the daemon's next start.`)}async function ut(){const t=await S.listAgents();if(t.length===0){console.log("No monitored agents found.");return}const o=g(h(),".dahlia","agents"),n=[];for(const s of t){const i=await k(o,s);n.push({agentId:s,mode:i.mode,reason:i.reason??"-",changed:i.timestamp?i.timestamp.split("T")[0]:"-"})}console.log(`Agent Mode Status
23
+ `),console.log(se(n));const e=n.filter(s=>s.mode!=="normal");if(e.length>0){console.log("");for(const s of e){const i=s.mode==="quarantined"?"QUARANTINED":"RESTRICTED";console.log(`\u26A0 ${s.agentId}: ${i}`)}}}async function ft(){const t=await S.listAgents();if(t.length===0){console.log("No monitored agents found. Create one with --create --agent=ID --name=NAME");return}const o=[];for(const e of t)try{const a=(await S.loadAgentProfile(e)).build().filter(I=>I.source==="agent-monitor"),d=a.length,c=a.length>0?a.map(I=>I.lastActive).sort().reverse()[0].split("T")[0]:"never",l=await new x(e).loadBaseline(e),f=await S.loadRole(e),y=[];if(l){const I=H(a,7),F=new _(l,f);for(const $ of I){const D={label:$.label,category:$.category,lastActive:$.lastActive,description:$.description,weight:$.weight,source:$.source,files:$.files,connections:$.connections,eventCount:$.eventCount};y.push(...F.analyzeSession(D))}}const w=g(h(),".dahlia","agents"),M=await k(w,e);o.push({agentId:e,sessions:d,lastActive:c,roleDefined:f!==null,baselineDefined:l!==null,status:O(d,y),mode:M.mode})}catch(s){console.warn(`Error loading agent ${e}:`,s),o.push({agentId:e,sessions:0,lastActive:"error",roleDefined:!1,baselineDefined:!1,status:"Error",mode:"normal"})}const n=o.filter(e=>e.mode&&e.mode!=="normal");if(n.length>0){for(const e of n){const s=e.mode==="quarantined"?"QUARANTINED":"RESTRICTED";console.log(`\u26A0 ${e.agentId}: ${s}`)}console.log("")}console.log(`Sentinel Agent Overview
24
+ `),console.log(ae(o))}async function pt(t){const o=await S.loadAgentProfile(t),n=await S.loadRole(t),s=await new x(t).loadBaseline(t),a=o.build().filter(D=>D.source==="agent-monitor"),d=H(a,7),c=d.map(ie),r=[];if(s){const D=new _(s,n);for(const A of d){const pe={label:A.label,category:A.category,lastActive:A.lastActive,description:A.description,weight:A.weight,source:A.source,files:A.files,connections:A.connections};r.push(...D.analyzeSession(pe))}}const l=g(h(),".dahlia","profile.json"),f=new ye(l),y=new we({backend:f});let w=[];await y.load()&&(w=y.build().filter(A=>A.core===!0).map(ie));const I=Me({agentId:t,agentName:t,role:n,baseline:s,e:c,findings:r,t:w}),F=new Date().toISOString().split("T")[0],$=`Sentinel Agent Report \u2014 ${t} \u2014 ${d.length} recent sessions \u2014 generated ${F}
25
25
 
26
- `;process.stdout.write($+I)}async function ct(e){const o=u(m(),".dahlia","agents"),n=new A(e,{logDir:u(o,e)});await n.open();const t=new N(e),a=await t.computeBaseline(n);await n.close(),await t.saveBaseline(a),console.log(`Baseline computed for agent: ${e}`),console.log(` Sessions: ${a.totalSessions}`),console.log(` Period: ${a.periodDays} days`),console.log(" Action distribution:");for(const[r,i]of Object.entries(a.actionDistribution))console.log(` ${r}: ${i}%`);console.log(` Saved to: ~/.dahlia/agents/${e}/baseline.json`)}async function dt(e,o,n){let t;if(n){let a;try{a=await M(n,"utf-8")}catch(r){console.error(`Failed to read role file "${n}":`,r);return}try{t=JSON.parse(a)}catch(r){console.error(`Invalid JSON in role file "${n}":`,r.message);return}}await S.createAgent(e,o,t),console.log(`Agent created: ${e} (${o})`),t&&console.log(` Role loaded from: ${n}`)}async function gt(){const e=u(m(),".dahlia","sentinel.json");try{await K(e),console.log(`Config already exists at ${e}`);return}catch{}await R(e,JSON.stringify({agents:[{agentId:"example-agent",name:"Example Agent",adapterType:"log",logPath:"/path/to/agent/activity.log",logFormat:"json-lines",roleDefinitionPath:"~/.dahlia/agents/example-agent/role.json"}],baselineWindowDays:30},null,2)+`
27
- `),console.log("Template config created at ~/.dahlia/sentinel.json \u2014 edit it with your agent details then run: npm run sentinel -- --monitor")}async function q(){const e=u(m(),".dahlia","sentinel.json");try{const o=await M(e,"utf-8");return JSON.parse(o)}catch(o){if(o.code==="ENOENT")return null;if(o instanceof SyntaxError)return console.error(`Invalid JSON in sentinel config: ${o.message}`),null;throw o}}async function ut(e){const o=await q();if(!o){console.log("No sentinel config found. Create ~/.dahlia/sentinel.json or use --init-config to add agents. See docs for config format.");return}let n=o.agents;if(e&&(n=n.filter(l=>l.agentId===e),n.length===0)){console.log(`Agent "${e}" not found in sentinel.json`);return}const t=new Map,a=new Map,r=l=>l&&l.startsWith("~/")?m()+l.slice(1):l;let i;if(o.alerts){const l={...o.alerts.minSeverity&&{minSeverity:o.alerts.minSeverity},...o.alerts.dedupeWindowMs!==void 0&&{dedupeWindowMs:o.alerts.dedupeWindowMs},...o.alerts.quietHoursEnabled!==void 0&&{quietHoursEnabled:o.alerts.quietHoursEnabled},...o.alerts.quietHours&&{quietHours:o.alerts.quietHours},...o.alerts.channels&&{channels:o.alerts.channels}};i=new ge(l)}for(const l of n)try{const g=new he(l.agentId,void 0,{type:l.adapterType,logPath:r(l.logPath),logFormat:l.logFormat,fieldMapping:l.fieldMapping,mcpLogDir:r(l.mcpLogDir),webhookPort:l.webhookPort,webhookApiKey:l.webhookApiKey,readExisting:l.readExisting});g.setAuditLogDir(u(m(),".dahlia","agents",l.agentId)),g.setFindingCallback(h=>{(h.severity==="HIGH"||h.severity==="CRITICAL")&&console.log(`
28
- \u26A0 [${h.agentId}] ${h.severity}: ${h.description}`)}),i&&g.setAlertManager(i),await g.start(),t.set(l.agentId,g),a.set(l.agentId,l.adapterType)}catch(g){console.error(`Failed to start monitoring for agent ${l.agentId}:`,g)}console.log(L(t,a));const d=setInterval(()=>{console.clear(),console.log(L(t,a))},5e3),c=async()=>{clearInterval(d);for(const[l,g]of t)try{await g.stop()}catch(h){console.warn(`Error stopping runner ${l}:`,h)}console.log(`
29
- `+ae(t)),process.exit(0)},s=()=>{c().catch(l=>{console.error("Error during shutdown:",l),process.exit(1)})};process.on("SIGINT",s),process.on("SIGTERM",s)}async function ft(e,o,n){const a=await new me(e).generateReport({periodDays:o,format:n});process.stdout.write(a)}async function pt(e,o){const n=await ye({periodDays:e,format:o});process.stdout.write(n)}async function mt(){const e=await q();if(!e){console.log("No sentinel config found. Create ~/.dahlia/sentinel.json or use --init-config.");return}const o=u(m(),".dahlia","agents"),n=new Map;try{for(const r of e.agents){const i=new A(r.agentId,{logDir:u(o,r.agentId)});await i.open(),n.set(r.agentId,i)}const a=await new ue().detect(n);if(a.length===0){console.log("No cross-agent correlations detected.");return}console.log(`Found ${a.length} cross-agent correlation(s):
30
- `);for(const r of a)console.log(`[${r.severity}] ${r.rule}`),console.log(` Agent A: ${r.agentA.agentId} \u2014 ${r.agentA.action} on ${r.agentA.target}`),console.log(` Agent B: ${r.agentB.agentId} \u2014 ${r.agentB.action} on ${r.agentB.target}`),console.log(` Time delta: ${Math.round(r.timeDeltaMs/1e3)}s`),console.log(` Recommendation: ${r.recommendation}`),console.log("")}finally{for(const t of n.values())await t.close()}}async function ht(e){const o=u(m(),".dahlia","agents"),n=u(o,e,"cumulative-stats.json");let t="(none)";try{t=JSON.parse(await M(n,"utf-8")).totalEntries}catch{}const r=await new A(e,{logDir:u(o,e)}).recomputeCumulativeStats();console.log(`Recomputed cumulative-stats for ${e}:`),console.log(` totalEntries: ${t} \u2192 ${r.totalEntries}`),console.log(` scope: ${r.countScope}`),console.log(" (Read-only over the signed trail; only cumulative-stats.json rewritten.)")}async function yt(e){const o=u(m(),".dahlia","agents"),n=u(o,e),t=await E(n),a=new A(e,{logDir:n});a.setSigningKey(t.privateKey,t.publicKey);const r=F&&x?[{file:F,reason:x}]:void 0,i=await a.enrollManifest(r?{quarantine:r}:void 0);console.log(`Manifest enrolled for ${e}: ${i.records} file record(s) signed + chained`+(i.quarantined?`, ${i.quarantined} quarantine record(s)`:"")+"."),r&&console.log(` Quarantined: ${F} \u2014 ${x}`),console.log(" (Read-only over audit entries; protects from enrollment forward.)")}async function wt(e){const o=u(m(),".dahlia","agents"),n=new A(e,{logDir:u(o,e)});await n.open();const t=await n.verify();if(t.valid?console.log(`Audit trail for ${e}: VALID (${t.totalEntries} entries verified)`):t.brokenAt!==void 0?console.log(`Audit trail for ${e}: BROKEN at entry ${t.brokenAt} (${t.totalEntries} entries checked)`):console.log(`Audit trail for ${e}: INVALID \u2014 file-level manifest integrity failure (${t.totalEntries} entries chain-verified)`),t.manifest){const r=t.manifest;console.log(` Manifest: ${r.recordCount} file record(s) \u2014 ${r.ok?"OK":`${r.issues.length} issue(s)`}`);for(const i of r.issues)console.log(` [${i.type}] ${i.detail}`);for(const i of r.quarantined)console.log(` [QUARANTINED] ${i.file} \u2014 ${i.reason} (retained on disk, excluded from verdict)`)}const a=await n.query({type:"finding",limit:1e4});if(a.length>0){let r=0,i=0,d=0;for(const s of a){const l=s.decision;l==="deny"?i++:l==="modify"?d++:r++}console.log(` Findings: ${a.length} total \u2014 ${r} allowed, ${i} denied, ${d} modified`);const c=a.slice(0,5);for(const s of c){const l=s,g=l.decision,h=l.modification,y=l.description,C=l._decisionDefaulted===!0;g==="modify"&&h?.type==="append_args"?console.log(` [modified] ${y} \u2014 appended args: [${h.args.join(", ")}]`):console.log(g==="deny"?` [denied] ${y}`:C?` [allowed (legacy)] ${y}`:` [allowed] ${y}`)}}await n.close()}async function $t(){const e=u(m(),".dahlia","agents"),o=new T({agentsDir:e}),t=await new H(e).listAgents();if(t.length===0){console.log("No monitored agents found."),await o.stop();return}const a=[];for(const r of t){const i=await o.getHealthScore(r);a.push({agentId:r,score:i.score,status:i.status,mode:i.mode,findings:`${i.findings.critical}/${i.findings.high}/${i.findings.medium}/${i.findings.low}`,lastEvent:i.lastEvent?i.lastEvent.split("T")[0]:"-",baseline:i.baselineEstablished})}await o.stop(),console.log(`Agent Health Dashboard
31
- `),console.log(ee(a))}var ie=`# Sentinel \u2014 Agent Security Policy
26
+ `;process.stdout.write($+I)}async function mt(t){const o=g(h(),".dahlia","agents"),n=new b(t,{logDir:g(o,t)});await n.open();const e=new x(t),s=await e.computeBaseline(n);await n.close(),await e.saveBaseline(s),console.log(`Baseline computed for agent: ${t}`),console.log(` Sessions: ${s.totalSessions}`),console.log(` Period: ${s.periodDays} days`),console.log(" Action distribution:");for(const[i,a]of Object.entries(s.actionDistribution))console.log(` ${i}: ${a}%`);console.log(` Saved to: ~/.dahlia/agents/${t}/baseline.json`)}async function ht(t,o,n){let e;if(n){let s;try{s=await N(n,"utf-8")}catch(i){console.error(`Failed to read role file "${n}":`,i);return}try{e=JSON.parse(s)}catch(i){console.error(`Invalid JSON in role file "${n}":`,i.message);return}}await S.createAgent(t,o,e),console.log(`Agent created: ${t} (${o})`),e&&console.log(` Role loaded from: ${n}`)}async function yt(){const t=g(h(),".dahlia","sentinel.json");try{await G(t),console.log(`Config already exists at ${t}`);return}catch{}await C(t,JSON.stringify({agents:[{agentId:"example-agent",name:"Example Agent",adapterType:"log",logPath:"/path/to/agent/activity.log",logFormat:"json-lines"}]},null,2)+`
27
+ `),console.log("Template config created at ~/.dahlia/sentinel.json \u2014 edit it with your agent details then run: npm run sentinel -- --monitor")}var wt=new Set(["log","webhook","mcp","manual"]),vt=new Set(["json-lines","csv"]),$t=new Set(["LOW","MEDIUM","HIGH","CRITICAL"]),ce=new Set(["informational","actionable"]),At=new Set(["console","webhook","file"]);function m(t){throw new Error(`sentinel.json validation error: ${t}`)}function j(t,o,n){for(const e of Object.keys(o))if(!n.includes(e)){const s=Te(e,n);m(`unknown key "${e}" in ${t}.`+(s?` Did you mean "${s}"?`:"")+` Valid keys: ${n.join(", ")}`)}}function de(t){(typeof t!="object"||t===null||Array.isArray(t))&&m("top level must be a JSON object");const o=t;o.baselineWindowDays!==void 0&&m("baselineWindowDays is not supported \u2014 it was never consumed; the baseline window is fixed at 30 days. Remove this field."),o.checkIntervalMs!==void 0&&m("checkIntervalMs is not supported \u2014 it was never consumed. Remove this field."),o.alertWebhook!==void 0&&m('alertWebhook is not supported \u2014 it was never consumed. Use alerts.channels with a webhook channel ({ "type": "webhook", "name": ..., "config": { "url": ... } }) instead.'),j("sentinel.json",o,["agents","alerts","baselineWindowDays","checkIntervalMs","alertWebhook"]),Array.isArray(o.agents)||m("agents must be an array");for(let n=0;n<o.agents.length;n++){const e=`agents[${n}]`,s=o.agents[n];if((typeof s!="object"||s===null)&&m(`${e} must be an object`),s.roleDefinitionPath!==void 0&&m(`${e}.roleDefinitionPath is not supported \u2014 it was never consumed; roles are loaded from the agent profile directory (~/.dahlia/agents/<id>/role.json). Remove this field.`),j(e,s,["agentId","name","adapterType","logPath","logFormat","fieldMapping","mcpLogDir","webhookPort","webhookApiKey","roleDefinitionPath","readExisting"]),(typeof s.agentId!="string"||s.agentId.length===0)&&m(`${e}.agentId is required and must be a non-empty string`),(typeof s.name!="string"||s.name.length===0)&&m(`${e}.name is required and must be a non-empty string`),(typeof s.adapterType!="string"||!wt.has(s.adapterType))&&m(`${e}.adapterType must be one of: log, webhook, mcp, manual`),s.logFormat!==void 0&&(typeof s.logFormat!="string"||!vt.has(s.logFormat))&&m(`${e}.logFormat must be one of: json-lines, csv`),s.webhookPort!==void 0){const i=P(s.webhookPort,{integer:!0,min:1,max:65535});i&&m(`${e}.webhookPort ${i}`)}s.readExisting!==void 0&&typeof s.readExisting!="boolean"&&m(`${e}.readExisting must be a boolean`)}if(o.alerts!==void 0){(typeof o.alerts!="object"||o.alerts===null)&&m("alerts must be an object");const n=o.alerts;if(j("alerts",n,["minSeverity","minKind","dedupeWindowMs","quietHoursEnabled","quietHours","channels"]),n.minSeverity!==void 0&&(typeof n.minSeverity!="string"||!$t.has(n.minSeverity))&&m("alerts.minSeverity must be one of: LOW, MEDIUM, HIGH, CRITICAL"),n.minKind!==void 0&&(typeof n.minKind!="string"||!ce.has(n.minKind))&&m("alerts.minKind must be one of: informational, actionable"),n.dedupeWindowMs!==void 0){const e=P(n.dedupeWindowMs,{min:0});e&&m(`alerts.dedupeWindowMs ${e}`)}if(n.quietHoursEnabled!==void 0&&typeof n.quietHoursEnabled!="boolean"&&m("alerts.quietHoursEnabled must be a boolean"),n.quietHours!==void 0){const e=n.quietHours,s=i=>P(i,{integer:!0,min:0,max:23});(!Array.isArray(e)||e.length!==2||s(e[0])||s(e[1]))&&m("alerts.quietHours must be [start, end] with integer values 0-23")}if(n.channels!==void 0){Array.isArray(n.channels)||m("alerts.channels must be an array");for(let e=0;e<n.channels.length;e++){const s=`alerts.channels[${e}]`,i=n.channels[e];(typeof i!="object"||i===null)&&m(`${s} must be an object`),j(s,i,["type","name","config","minKind"]),(typeof i.type!="string"||!At.has(i.type))&&m(`${s}.type must be one of: console, webhook, file`),i.minKind!==void 0&&(typeof i.minKind!="string"||!ce.has(i.minKind))&&m(`${s}.minKind must be one of: informational, actionable`),i.config!==void 0&&((typeof i.config!="object"||i.config===null)&&m(`${s}.config must be an object`),j(`${s}.config`,i.config,["url","headers","filePath"]))}}}return t}async function W(){const t=g(h(),".dahlia","sentinel.json");let o;try{o=await N(t,"utf-8")}catch(e){if(e.code==="ENOENT")return null;throw e}let n;try{n=JSON.parse(o)}catch(e){if(e instanceof SyntaxError)return console.error(`Invalid JSON in sentinel config: ${e.message}`),null;throw e}return de(n)}async function bt(t){const o=await W();if(!o){console.log("No sentinel config found. Create ~/.dahlia/sentinel.json or use --init-config to add agents. See docs for config format.");return}let n=o.agents;if(t&&(n=n.filter(l=>l.agentId===t),n.length===0)){console.log(`Agent "${t}" not found in sentinel.json`);return}const e=new Map,s=new Map,i=l=>l&&l.startsWith("~/")?h()+l.slice(1):l;let a;if(o.alerts){const l={...o.alerts.minSeverity&&{minSeverity:o.alerts.minSeverity},...o.alerts.dedupeWindowMs!==void 0&&{dedupeWindowMs:o.alerts.dedupeWindowMs},...o.alerts.quietHoursEnabled!==void 0&&{quietHoursEnabled:o.alerts.quietHoursEnabled},...o.alerts.quietHours&&{quietHours:o.alerts.quietHours},...o.alerts.minKind&&{minKind:o.alerts.minKind},...o.alerts.channels&&{channels:o.alerts.channels}};a=new me(l)}for(const l of n)try{const f=new $e(l.agentId,void 0,{type:l.adapterType,logPath:i(l.logPath),logFormat:l.logFormat,fieldMapping:l.fieldMapping,mcpLogDir:i(l.mcpLogDir),webhookPort:l.webhookPort,webhookApiKey:l.webhookApiKey,readExisting:l.readExisting});f.setAuditLogDir(g(h(),".dahlia","agents",l.agentId)),f.setFindingCallback(y=>{(y.severity==="HIGH"||y.severity==="CRITICAL")&&console.log(`
28
+ \u26A0 [${y.agentId}] ${y.severity}: ${y.description}`)}),a&&f.setAlertManager(a),await f.start(),e.set(l.agentId,f),s.set(l.agentId,l.adapterType)}catch(f){console.error(`Failed to start monitoring for agent ${l.agentId}:`,f)}console.log(K(e,s));const d=setInterval(()=>{console.clear(),console.log(K(e,s))},5e3),c=async()=>{clearInterval(d);for(const[l,f]of e)try{await f.stop()}catch(y){console.warn(`Error stopping runner ${l}:`,y)}console.log(`
29
+ `+re(e)),process.exit(0)},r=()=>{c().catch(l=>{console.error("Error during shutdown:",l),process.exit(1)})};process.on("SIGINT",r),process.on("SIGTERM",r)}async function It(t,o,n){const s=await new ve(t).generateReport({periodDays:o,format:n});process.stdout.write(s)}async function St(t,o){const n=await Ae({periodDays:t,format:o});process.stdout.write(n)}async function Dt(t=30){const o=await W();if(!o){console.log("No sentinel config found. Create ~/.dahlia/sentinel.json or use --init-config.");return}const n=g(h(),".dahlia","agents"),e=new Map;try{for(const a of o.agents){const d=new b(a.agentId,{logDir:g(n,a.agentId)});await d.open(),e.set(a.agentId,d)}const i=await new he().detect(e,t*24*60*60*1e3);if(i.length===0){console.log("No cross-agent correlations detected.");return}console.log(`Found ${i.length} cross-agent correlation(s):
30
+ `);for(const a of i)console.log(`[${a.severity}] ${a.rule}`),console.log(` Agent A: ${a.agentA.agentId} \u2014 ${a.agentA.action} on ${a.agentA.target}`),console.log(` Agent B: ${a.agentB.agentId} \u2014 ${a.agentB.action} on ${a.agentB.target}`),console.log(` Time delta: ${Math.round(a.timeDeltaMs/1e3)}s`),console.log(` Recommendation: ${a.recommendation}`),console.log("")}finally{for(const s of e.values())await s.close()}}async function Tt(t){const o=g(h(),".dahlia","agents"),n=g(o,t,"cumulative-stats.json");let e="(none)";try{e=JSON.parse(await N(n,"utf-8")).totalEntries}catch{}const i=await new b(t,{logDir:g(o,t)}).recomputeCumulativeStats();console.log(`Recomputed cumulative-stats for ${t}:`),console.log(` totalEntries: ${e} \u2192 ${i.totalEntries}`),console.log(` scope: ${i.countScope}`),console.log(" (Read-only over the signed trail; only cumulative-stats.json rewritten.)")}async function kt(t){const o=g(h(),".dahlia","agents"),n=g(o,t),e=await R(n),s=new b(t,{logDir:n});s.setSigningKey(e.privateKey,e.publicKey);const i=L&&q?[{file:L,reason:q}]:void 0,a=await s.enrollManifest(i?{quarantine:i}:void 0);console.log(`Manifest enrolled for ${t}: ${a.records} file record(s) signed + chained`+(a.quarantined?`, ${a.quarantined} quarantine record(s)`:"")+"."),i&&console.log(` Quarantined: ${L} \u2014 ${q}`),console.log(" (Read-only over audit entries; protects from enrollment forward.)")}async function Et(t){const o=g(h(),".dahlia","agents"),n=new b(t,{logDir:g(o,t)});await n.open();const e=await n.verify();if(e.valid?console.log(`Audit trail for ${t}: VALID (${e.totalEntries} entries verified)`):e.brokenAt!==void 0?console.log(`Audit trail for ${t}: BROKEN at entry ${e.brokenAt} (${e.totalEntries} entries checked)`):console.log(`Audit trail for ${t}: INVALID \u2014 file-level manifest integrity failure (${e.totalEntries} entries chain-verified)`),e.manifest){const i=e.manifest;console.log(` Manifest: ${i.recordCount} file record(s) \u2014 ${i.ok?"OK":`${i.issues.length} issue(s)`}`);for(const a of i.issues)console.log(` [${a.type}] ${a.detail}`);for(const a of i.quarantined)console.log(` [QUARANTINED] ${a.file} \u2014 ${a.reason} (retained on disk, excluded from verdict)`)}const s=await n.query({type:"finding",limit:1e4});if(s.length>0){let i=0,a=0,d=0;for(const r of s){const l=r.decision;l==="deny"?a++:l==="modify"?d++:i++}console.log(` Findings: ${s.length} total \u2014 ${i} allowed, ${a} denied, ${d} modified`);const c=s.slice(0,5);for(const r of c){const l=r,f=l.decision,y=l.modification,w=l.description,M=l._decisionDefaulted===!0;f==="modify"&&y?.type==="append_args"?console.log(` [modified] ${w} \u2014 appended args: [${y.args.join(", ")}]`):console.log(f==="deny"?` [denied] ${w}`:M?` [allowed (legacy)] ${w}`:` [allowed] ${w}`)}}await n.close()}async function Ct(){const t=g(h(),".dahlia","agents"),o=new E({agentsDir:t}),e=await new B(t).listAgents();if(e.length===0){console.log("No monitored agents found."),await o.stop();return}const s=[];for(const i of e){const a=await o.getHealthScore(i);s.push({agentId:i,score:a.score,status:a.status,mode:a.mode,findings:`${a.findings.critical}/${a.findings.high}/${a.findings.medium}/${a.findings.low}`,lastEvent:a.lastEvent?a.lastEvent.split("T")[0]:"-",baseline:a.baselineEstablished})}await o.stop(),console.log(`Agent Health Dashboard
31
+ `),console.log(oe(s))}var ge=`# Sentinel \u2014 Agent Security Policy
32
32
  version: "1.0"
33
33
 
34
34
  agent:
@@ -56,8 +56,6 @@ policy:
56
56
  # schedule:
57
57
  # hours: [9, 18]
58
58
  # days: [Monday, Tuesday, Wednesday, Thursday, Friday]
59
- # limits:
60
- # maxEventsPerHour: 500
61
59
 
62
60
  # enforcement:
63
61
  # restrictAfter: 2
@@ -69,12 +67,12 @@ policy:
69
67
  # minSeverity: MEDIUM
70
68
 
71
69
  # repo:
72
- # repoRoot: . # scan this directory for sensitive files
70
+ # root: . # scan this directory for sensitive files
73
71
  # mapPath: ~/.dahlia/repo-sensitivity.json
74
72
  # overlayPath: ~/.dahlia/repo-sensitivity.review.json
75
- `;async function re(){const e=u(process.cwd(),".sentinel.yaml");try{await K(e),console.log(`.sentinel.yaml already exists in ${process.cwd()}`);return}catch{}await R(e,ie,"utf-8"),console.log("Created .sentinel.yaml \u2014 edit then run: npm run sentinel -- --from-policy .sentinel.yaml")}async function le(e){const o=await ve(e),n=await T.fromPolicy(e);console.log(`Loaded policy for agent ${o.agent.id}. Monitoring active.`);const t=async()=>{await n.stop(),console.log("Monitoring stopped."),process.exit(0)},a=()=>{t().catch(r=>{console.error("Error during shutdown:",r),process.exit(1)})};process.on("SIGINT",a),process.on("SIGTERM",a)}async function vt(e,o,n){const t=u(m(),".dahlia","agents"),a=new T({agentsDir:t});await a.addAgent(e,e);const r=z?z.split(","):void 0,i=Y?Y.split(",").map(c=>c.trim()):void 0,d=a.startTask(e,o,n,{relaxedActions:r,phases:i});if(!d){console.log(`Failed to start task for agent ${e}.`),await a.stop();return}console.log(`Task started for agent ${e}:`),console.log(` Task ID: ${d.taskId}`),console.log(` Description: ${d.description}`),console.log(` Keywords: ${d.keywords.join(", ")}`),d.relaxedActions?.length&&console.log(` Relaxed: ${d.relaxedActions.join(", ")}`),d.phases?.length&&console.log(` Phases: ${d.phases.join(", ")}`),console.log(` TTL: ${(d.ttlMs/6e4).toFixed(0)} minutes`),await a.stop()}async function At(e){const o=u(m(),".dahlia","agents"),n=new T({agentsDir:o});await n.addAgent(e,e);const t=n.endTask(e);t?(console.log(`Task ended for agent ${e}:`),console.log(` Task ID: ${t.taskId}`),console.log(` Description: ${t.description}`),console.log(` Status: ${t.status}`)):console.log(`No active task found for agent ${e}.`),await n.stop()}async function It(e){const o=u(m(),".dahlia","agents"),n=new T({agentsDir:o});await n.addAgent(e,e);const t=n.getActiveTask(e);if(!t){console.log(`No active task for agent ${e}.`),await n.stop();return}const a=Date.now()-new Date(t.startedAt).getTime(),r=t.ttlMs-a;console.log(`Active task for agent ${e}:
76
- `),console.log(` Task ID: ${t.taskId}`),console.log(` Description: ${t.description}`),console.log(` Keywords: ${t.keywords.join(", ")}`),console.log(` Status: ${t.status}`),console.log(` Active for: ${(a/6e4).toFixed(1)} minutes`),console.log(` TTL remain: ${r>0?(r/6e4).toFixed(1)+" minutes":"EXPIRED"}`),t.relaxedActions?.length&&console.log(` Relaxed: ${t.relaxedActions.join(", ")}`),t.phases?.length&&console.log(` Phases: ${t.phases.join(", ")}`),t.acceptableActions.length>0&&console.log(` Acceptable: ${t.acceptableActions.map(i=>`${i.action}:${i.targetPattern}`).join(", ")}`),await n.stop()}async function St(e){const o=u(m(),".dahlia","agents"),n=new A(e,{logDir:u(o,e)});await n.open();const t=await n.getStats(),a=await n.query({type:"intent_check"}),i=(await n.query({type:"finding"})).filter(c=>typeof c.data=="object"&&c.data!==null&&"type"in c.data&&c.data.type==="intent_drift");console.log(`Intent Alignment Report \u2014 ${e}
77
- `),console.log(` Intent starts: ${t.intentStartCount}`),console.log(` Intent ends: ${t.intentEndCount}`),console.log(` Intent checks: ${t.intentCheckCount}`),console.log(` Drift findings: ${t.intentDriftCount}`),t.averageAlignmentScore!==null&&console.log(` Avg alignment: ${t.averageAlignmentScore.toFixed(2)}`);const d=a.filter(c=>{const s=c.data;return s&&typeof s=="object"&&"aligned"in s&&!s.aligned}).slice(-5);if(d.length>0){console.log(`
78
- Last ${d.length} misaligned action(s):`);for(const c of d){const s=c.data,l=typeof s.score=="number"?s.score.toFixed(2):"?",g=s.event,h=g?.action??"?",y=g?.primaryTarget??g?.targets?.[0]??g?.target??"?";console.log(` score: ${l} ${h} \u2192 ${y} (${c.timestamp.split("T")[0]})`)}}if(i.length>0){console.log(`
79
- Recent drift findings: ${i.length}`);for(const c of i.slice(-3)){const s=c.data,l=s.severity??"?",g=s.description??"";console.log(` [${l}] ${typeof g=="string"?g.slice(0,80):g}`)}}await n.close()}async function Dt(e,o){try{const n=await de({force:e,port:o});if(n.created.length>0){console.log("Created:");for(const t of n.created)console.log(` ${t}`)}if(n.skipped.length>0){console.log("Skipped (already exists \u2014 use --force to overwrite):");for(const t of n.skipped)console.log(` ${t}`)}if(n.merged.length>0){console.log("Merged:");for(const t of n.merged)console.log(` ${t}`)}if(n.errors.length>0){console.log("Errors:");for(const t of n.errors)console.log(` ${t}`)}n.errors.length===0&&console.log(`
80
- Sentinel + Claude Code integration ready.`)}catch(n){console.error(`Init failed: ${n.message}`),process.exit(1)}}if(Le){const e=w(f,"--port");await Dt(qe,e?parseInt(e,10):void 0)}else if(Ye&&p&&Q&&V)await vt(p,Q,V);else if(Xe&&p)await At(p);else if(Ze&&p)await It(p);else if(et&&p)await St(p);else if(Oe)await re();else if(_)await le(_);else if(Ge&&p)await yt(p);else if(Je&&p)await ht(p);else if(We&&p)await wt(p);else if(Ue)await $t();else if(_e&&p)await tt(p,X??"No reason provided");else if(Qe&&p)await nt(p,X??"No reason provided");else if(Ve)await st(p);else if(ze)await it();else if(Pe)await gt();else if(xe)await ut(p);else if(Ne&&p&&U)await dt(p,U,ke);else if(Fe&&p)await ct(p);else if(Be){const e=j?parseInt(j,10):30;await pt(e,Z==="json"?"json":"markdown")}else if(He&&p){const e=j?parseInt(j,10):30;await ft(p,e,Z==="json"?"json":"markdown")}else Ke?await mt():p?await lt(p):await rt();
73
+ `;async function ue(){const t=g(process.cwd(),".sentinel.yaml");try{await G(t),console.log(`.sentinel.yaml already exists in ${process.cwd()}`);return}catch{}await C(t,ge,"utf-8"),console.log("Created .sentinel.yaml \u2014 edit then run: npm run sentinel -- --from-policy .sentinel.yaml")}async function fe(t){const o=await De(t),n=await E.fromPolicy(t);console.log(`Loaded policy for agent ${o.agent.id}. Monitoring active.`);const e=async()=>{await n.stop(),console.log("Monitoring stopped."),process.exit(0)},s=()=>{e().catch(i=>{console.error("Error during shutdown:",i),process.exit(1)})};process.on("SIGINT",s),process.on("SIGTERM",s)}async function jt(t,o,n){const e=g(h(),".dahlia","agents"),s=new E({agentsDir:e});await s.addAgent(t,t);const i=Z?Z.split(","):void 0,a=ee?ee.split(",").map(c=>c.trim()):void 0,d=s.startTask(t,o,n,{relaxedActions:i,phases:a});if(!d){console.log(`Failed to start task for agent ${t}.`),await s.stop();return}console.log(`Task started for agent ${t}:`),console.log(` Task ID: ${d.taskId}`),console.log(` Description: ${d.description}`),console.log(` Keywords: ${d.keywords.join(", ")}`),d.relaxedActions?.length&&console.log(` Relaxed: ${d.relaxedActions.join(", ")}`),d.phases?.length&&console.log(` Phases: ${d.phases.join(", ")}`),console.log(` TTL: ${(d.ttlMs/6e4).toFixed(0)} minutes`),await s.stop()}async function Mt(t){const o=g(h(),".dahlia","agents"),n=new E({agentsDir:o});await n.addAgent(t,t);const e=n.endTask(t);e?(console.log(`Task ended for agent ${t}:`),console.log(` Task ID: ${e.taskId}`),console.log(` Description: ${e.description}`),console.log(` Status: ${e.status}`)):console.log(`No active task found for agent ${t}.`),await n.stop()}async function Rt(t){const o=g(h(),".dahlia","agents"),n=new E({agentsDir:o});await n.addAgent(t,t);const e=n.getActiveTask(t);if(!e){console.log(`No active task for agent ${t}.`),await n.stop();return}const s=Date.now()-new Date(e.startedAt).getTime(),i=e.ttlMs-s;console.log(`Active task for agent ${t}:
74
+ `),console.log(` Task ID: ${e.taskId}`),console.log(` Description: ${e.description}`),console.log(` Keywords: ${e.keywords.join(", ")}`),console.log(` Status: ${e.status}`),console.log(` Active for: ${(s/6e4).toFixed(1)} minutes`),console.log(` TTL remain: ${i>0?(i/6e4).toFixed(1)+" minutes":"EXPIRED"}`),e.relaxedActions?.length&&console.log(` Relaxed: ${e.relaxedActions.join(", ")}`),e.phases?.length&&console.log(` Phases: ${e.phases.join(", ")}`),e.acceptableActions.length>0&&console.log(` Acceptable: ${e.acceptableActions.map(a=>`${a.action}:${a.targetPattern}`).join(", ")}`),await n.stop()}async function Nt(t){const o=g(h(),".dahlia","agents"),n=new b(t,{logDir:g(o,t)});await n.open();const e=await n.getStats(),s=await n.query({type:"intent_check"}),a=(await n.query({type:"finding"})).filter(c=>c.findingType==="intent_drift");console.log(`Intent Alignment Report \u2014 ${t}
75
+ `),console.log(` Intent starts: ${e.intentStartCount}`),console.log(` Intent ends: ${e.intentEndCount}`),console.log(` Intent checks: ${e.intentCheckCount}`),console.log(` Drift findings: ${e.intentDriftCount}`),e.averageAlignmentScore!==null&&console.log(` Avg alignment: ${e.averageAlignmentScore.toFixed(2)}`);const d=s.filter(c=>c.aligned===!1).slice(0,5);if(d.length>0){console.log(`
76
+ Last ${d.length} misaligned action(s):`);for(const c of d){const r=typeof c.score=="number"?c.score.toFixed(2):"?",l=c.action??"?",f=c.primaryTarget??c.targets?.[0]??"?";console.log(` score: ${r} ${l} \u2192 ${f} (${c.timestamp.split("T")[0]})`)}}if(a.length>0){console.log(`
77
+ Recent drift findings: ${a.length}`);for(const c of a.slice(0,3)){const r=c.severity??"?",l=c.description??"";console.log(` [${r}] ${typeof l=="string"?l.slice(0,80):l}`)}}await n.close()}async function Ft(t,o){try{const n=await be({force:t,port:o});if(n.created.length>0){console.log("Created:");for(const e of n.created)console.log(` ${e}`)}if(n.skipped.length>0){console.log("Skipped (already exists \u2014 use --force to overwrite):");for(const e of n.skipped)console.log(` ${e}`)}if(n.merged.length>0){console.log("Merged:");for(const e of n.merged)console.log(` ${e}`)}if(n.errors.length>0){console.log("Errors:");for(const e of n.errors)console.log(` ${e}`)}n.errors.length===0&&console.log(`
78
+ Sentinel + Claude Code integration ready.`)}catch(n){console.error(`Init failed: ${n.message}`),process.exit(1)}}if(_e){const t=v(u,"--port");await Ft(Ge,t?parseInt(t,10):void 0)}else if(ot&&p&&Y&&X)await jt(p,Y,X);else if(st&&p)await Mt(p);else if(at&&p)await Rt(p);else if(it&&p)await Nt(p);else if(Be)await ue();else if(z)await fe(z);else if(ze&&p)await kt(p);else if(Ye&&p)await Tt(p);else if(Qe&&p)await Et(p);else if(Xe)await Ct();else if(Ze&&p)await rt(p,te??"No reason provided");else if(et&&p)await lt(p,te??"No reason provided");else if(tt)await gt(p);else if(nt)await ut();else if(We)await yt();else if(Ke)await bt(p);else if(Oe&&p&&Q)await ht(p,Q,qe);else if(He&&p)await mt(p);else if(Je){const t=T?parseInt(T,10):30;await St(t,ne==="json"?"json":"markdown")}else if(Ue&&p){const t=T?parseInt(T,10):30;await It(p,t,ne==="json"?"json":"markdown")}else if(Ve){const t=T?parseInt(T,10):30;await Dt(t)}else p?await pt(p):await ft();
@@ -1,4 +1,4 @@
1
- import { v as Sentinel, e as AgentRole, S as SecurityFinding } from '../Sentinel-xFCyXH45.js';
1
+ import { v as Sentinel, e as AgentRole, S as SecurityFinding } from '../Sentinel-DT0IyGQi.js';
2
2
  import 'node:crypto';
3
3
 
4
4
  /**
@@ -75,6 +75,13 @@ interface SentinelGatewayOptions {
75
75
  * Operator-only, same channel as unknownTools.
76
76
  */
77
77
  allowUnknownTools?: string[];
78
+ /**
79
+ * Build identity reported via /health (daemon-staleness fix). Content hash of
80
+ * the daemon entry file this process was launched from; session-start compares
81
+ * it to the hash of the current on-disk entry and relaunches on mismatch.
82
+ * Absent → /health reports "unknown" → next session-start relaunches (safe side).
83
+ */
84
+ buildId?: string;
78
85
  }
79
86
  declare class SentinelGateway {
80
87
  private readonly configuredPort;
@@ -90,6 +97,9 @@ declare class SentinelGateway {
90
97
  private readonly releaseToken;
91
98
  /** Item D (F-8): disposition for unknown (non-MCP, unrecognized) tool names. */
92
99
  private readonly unknownTools;
100
+ /** Daemon-staleness build identity (content hash of the launched-from entry),
101
+ * reported via /health. "unknown" when not supplied by the launcher. */
102
+ private readonly buildId;
93
103
  private server;
94
104
  private running;
95
105
  private signalHandlersInstalled;
@@ -164,6 +174,15 @@ declare class SentinelGateway {
164
174
  * when the isolation gate is ON.
165
175
  */
166
176
  private resolveBPathRouting;
177
+ /**
178
+ * Forbidden-pattern list for ONE request: the gateway's construction-time
179
+ * list unioned with the request's merged-role patterns (operator-ceiling ∩
180
+ * workspace yaml). Without this, a workspace's custom forbid targets were
181
+ * enforced by the role validator inside wrap() but never reached the
182
+ * gateway's own bash-L1 / Grep layers, which checked only the static list.
183
+ * Normalization is idempotent (the chokepoint globstar-prepend).
184
+ */
185
+ private patternsForRequest;
167
186
  private handlePreToolUse;
168
187
  private handlePostToolUse;
169
188
  private handleSessionEnd;
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  SentinelGateway
3
- } from "../chunk-L4R3LPJS.js";
3
+ } from "../chunk-HRI2Y326.js";
4
4
  import "../chunk-B5QKJHSV.js";
5
- import "../chunk-FMZWHT4M.js";
6
- import "../chunk-QIYQWOLO.js";
7
- import "../chunk-WLIDSTS4.js";
5
+ import "../chunk-B6S2PBS4.js";
6
+ import "../chunk-FIEIGBYL.js";
7
+ import "../chunk-KWZ7JKKO.js";
8
8
  export {
9
9
  SentinelGateway
10
10
  };
@@ -1,26 +1,48 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runGatewayDaemon
4
- } from "./chunk-L4R3LPJS.js";
4
+ } from "./chunk-HRI2Y326.js";
5
+ import {
6
+ computeBuildId,
7
+ runSessionStart
8
+ } from "./chunk-LTBVWF5H.js";
9
+ import "./chunk-LATQNIRW.js";
5
10
  import "./chunk-B5QKJHSV.js";
6
- import "./chunk-FMZWHT4M.js";
7
- import "./chunk-QIYQWOLO.js";
8
- import "./chunk-WLIDSTS4.js";
11
+ import "./chunk-B6S2PBS4.js";
12
+ import "./chunk-FIEIGBYL.js";
13
+ import "./chunk-KWZ7JKKO.js";
9
14
 
10
15
  // src/gatewayDaemon.ts
16
+ import { fileURLToPath } from "url";
11
17
  var args = process.argv.slice(2);
12
- var policyPath;
13
- var port;
14
- for (let i = 0; i < args.length; i++) {
15
- if (args[i] === "--policy" && args[i + 1]) policyPath = args[++i];
16
- else if (args[i] === "--port" && args[i + 1]) port = parseInt(args[++i], 10);
18
+ var selfPath = fileURLToPath(import.meta.url);
19
+ function getFlag(name) {
20
+ for (let i = 0; i < args.length; i++) {
21
+ if (args[i] === name && args[i + 1]) return args[i + 1];
22
+ }
23
+ return void 0;
17
24
  }
18
- if (!policyPath) {
19
- console.error("[SENTINEL GATEWAY] --policy <path> is required");
20
- process.exit(1);
25
+ if (args.includes("--session-start")) {
26
+ const portArg = getFlag("--port");
27
+ runSessionStart({
28
+ cwd: getFlag("--cwd"),
29
+ port: portArg ? parseInt(portArg, 10) : void 0,
30
+ gatewayEntry: selfPath
31
+ }).catch((err) => {
32
+ console.error("[SENTINEL GATEWAY] session-start error:", err);
33
+ process.exit(0);
34
+ });
35
+ } else {
36
+ const policyPath = getFlag("--policy");
37
+ const portArg = getFlag("--port");
38
+ const port = portArg ? parseInt(portArg, 10) : void 0;
39
+ if (!policyPath) {
40
+ console.error("[SENTINEL GATEWAY] --policy <path> is required");
41
+ process.exit(1);
42
+ }
43
+ runGatewayDaemon({ policyPath, port, buildId: computeBuildId(selfPath) }).catch((err) => {
44
+ console.error("[SENTINEL GATEWAY] Fatal:", err);
45
+ process.exit(1);
46
+ });
21
47
  }
22
- runGatewayDaemon({ policyPath, port }).catch((err) => {
23
- console.error("[SENTINEL GATEWAY] Fatal:", err);
24
- process.exit(1);
25
- });
26
48
  //# sourceMappingURL=gatewayDaemon.js.map
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as AgentActivityEvent, S as SecurityFinding } from './Sentinel-xFCyXH45.js';
2
- export { a as AcceptableAction, b as AdapterConfig, c as AgentBaseline, d as AgentMode, e as AgentRole, f as AlertChannel, g as AlertConfig, h as AllowResponse, i as AuditEntry, j as AuditQueryOptions, B as BlockResponse, C as CorrelationFinding, E as ExceptionApprovalContext, k as ExceptionApprovalFn, G as GuideResponse, H as HookCheckpoint, l as HookContext, m as HookHandler, n as HookRegistration, o as HookResponse, I as IntentAlignmentConfig, p as IntentAlignmentResult, M as ModifiableEventFields, q as MonitorOptions, O as OverlayDecisionType, R as RepoSensitivityMap, r as ReportOptions, s as RoleException, t as SecuritySeverity, u as SensitivityOverlay, v as Sentinel, w as SentinelConfig, T as TaskIntent } from './Sentinel-xFCyXH45.js';
1
+ import { A as AgentActivityEvent, S as SecurityFinding } from './Sentinel-DT0IyGQi.js';
2
+ export { a as AcceptableAction, b as AdapterConfig, c as AgentBaseline, d as AgentMode, e as AgentRole, f as AlertChannel, g as AlertConfig, h as AllowResponse, i as AuditEntry, j as AuditQueryOptions, B as BlockResponse, C as CorrelationFinding, E as ExceptionApprovalContext, k as ExceptionApprovalFn, G as GuideResponse, H as HookCheckpoint, l as HookContext, m as HookHandler, n as HookRegistration, o as HookResponse, I as IntentAlignmentConfig, p as IntentAlignmentResult, M as ModifiableEventFields, q as MonitorOptions, O as OverlayDecisionType, R as RepoSensitivityMap, r as ReportOptions, s as RoleException, t as SecuritySeverity, u as SensitivityOverlay, v as Sentinel, w as SentinelConfig, T as TaskIntent } from './Sentinel-DT0IyGQi.js';
3
3
  import 'node:crypto';
4
4
 
5
5
  interface SentinelPolicy {
@@ -35,17 +35,11 @@ interface SentinelPolicy {
35
35
  hours: [number, number];
36
36
  days?: string[];
37
37
  };
38
- limits?: {
39
- maxEventsPerHour?: number;
40
- maxSessionDuration?: number;
41
- };
42
38
  };
43
39
  enforcement?: {
44
40
  restrictAfter?: number;
45
41
  quarantineAfter?: number;
46
42
  approvalRequired?: boolean;
47
- /** Minimum finding kind that triggers enforcement actions. Defaults to "actionable". */
48
- minKind?: "informational" | "actionable";
49
43
  /**
50
44
  * List of finding types to promote from "informational" to "actionable".
51
45
  * Cannot include LOCKED_ACTIONABLE_TYPES (they are already actionable).
@@ -121,30 +115,48 @@ declare function runInitClaudeCode(options: {
121
115
  home?: string;
122
116
  }): Promise<InitReport>;
123
117
 
124
- /**
125
- * Walks up from a given directory looking for .sentinel.yaml.
126
- * Stops at $HOME (inclusive) or filesystem root.
127
- */
128
118
  declare function discoverPolicy(startDir: string, home: string): string | null;
129
119
 
130
120
  /**
131
- * `session-start` entry point — called by the hook script on SessionStart.
121
+ * `session-start` entry point — called by the hook script on SessionStart, and
122
+ * the single source of truth for the gateway lifecycle decision (anti-drift: the
123
+ * cc hook delegates here via the dual-mode daemon entry rather than carrying its
124
+ * own copy of this logic).
132
125
  *
133
- * 1. Discover .sentinel.yaml (walk up from cwd to $HOME)
134
- * 2. Acquire gateway lock (PID file check)
135
- * 3. If gateway already running exit early
136
- * 4. If no policy found → exit early (no gateway needed)
137
- * 5. Spawn gateway detached, write PID file, unref
126
+ * Flow:
127
+ * 1. Discover .sentinel.yaml (walk up from cwd to $HOME); none → no gateway.
128
+ * 2. Resolve the daemon entry AT LAUNCH (not a baked path) and hash it — the
129
+ * current build identity.
130
+ * 3. Liveness: is a daemon already running (PID file)?
131
+ * - none → cold spawn (today's path).
132
+ * - running → GET /health, compare its build id to the current entry hash:
133
+ * match → reuse (unchanged fast path).
134
+ * mismatch → RELAUNCH: SIGTERM (→ SIGKILL fallback) the stale daemon,
135
+ * spawn the current build, wait ready. This is pure process
136
+ * replacement: it writes NO mode_change / release / trail
137
+ * state — the new daemon rebuilds mode (mode.json) and count
138
+ * (the append-only trail) lazily via _initWorkspaceAgent, so
139
+ * a restricted/quarantined agent stays restricted/quarantined
140
+ * with the same escalation count across the relaunch.
141
+ * Concurrency: a relaunch holds an atomic lock so two simultaneous session-
142
+ * starts converge on ONE daemon (the loser waits for ready and reuses) instead
143
+ * of racing two daemons onto the port.
138
144
  */
139
145
  interface SessionStartResult {
140
- action: "reused" | "spawned" | "no-policy";
146
+ action: "reused" | "spawned" | "relaunched" | "no-policy";
141
147
  pid?: number;
142
148
  policyPath?: string;
149
+ /** Why a relaunch happened (build mismatch) or null — for telemetry/tests. */
150
+ relaunchReason?: string | null;
143
151
  }
144
152
  declare function runSessionStart(options?: {
145
153
  cwd?: string;
146
154
  home?: string;
147
155
  port?: number;
156
+ /** Daemon entry to launch. Defaults to resolveGatewayEntryPoint() (resolved at
157
+ * launch, NOT a baked path — closes the pnpm-relocation / dangling cases). The
158
+ * dual-mode entry passes its own path so the launcher spawns the current build. */
159
+ gatewayEntry?: string;
148
160
  }): Promise<SessionStartResult>;
149
161
 
150
162
  export { AgentActivityEvent, type CliApprovalOptions, type InitReport, SecurityFinding, type SentinelPolicy, type SessionStartResult, createCliApproval, discoverPolicy, loadPolicy, loadPolicyFromString, runInitClaudeCode, runSessionStart };
package/dist/index.js CHANGED
@@ -1,20 +1,21 @@
1
- import {
2
- runInitClaudeCode,
3
- runSessionStart
4
- } from "./chunk-FWIISAZZ.js";
1
+ import "./chunk-TKAKHSZ3.js";
5
2
  import {
6
3
  Sentinel,
7
4
  createCliApproval
8
- } from "./chunk-GRN5P3H2.js";
5
+ } from "./chunk-I2FVDDSG.js";
6
+ import {
7
+ runInitClaudeCode,
8
+ runSessionStart
9
+ } from "./chunk-LTBVWF5H.js";
9
10
  import "./chunk-LATQNIRW.js";
10
11
  import {
11
12
  discoverPolicy
12
- } from "./chunk-FMZWHT4M.js";
13
- import "./chunk-QIYQWOLO.js";
13
+ } from "./chunk-B6S2PBS4.js";
14
+ import "./chunk-FIEIGBYL.js";
14
15
  import {
15
16
  loadPolicy,
16
17
  loadPolicyFromString
17
- } from "./chunk-WLIDSTS4.js";
18
+ } from "./chunk-KWZ7JKKO.js";
18
19
  import "./chunk-NUXSUSYY.js";
19
20
  export {
20
21
  Sentinel,
@@ -0,0 +1,7 @@
1
+ import {
2
+ LogAdapter
3
+ } from "./chunk-2IPSTUNH.js";
4
+ export {
5
+ LogAdapter
6
+ };
7
+ //# sourceMappingURL=logAdapter-WM43W3S7.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  LogAdapter
3
- } from "./chunk-PDWWRZXF.js";
3
+ } from "./chunk-2IPSTUNH.js";
4
4
 
5
5
  // src/adapters/mcpAdapter.ts
6
6
  var ACTION_MAP = {
@@ -175,4 +175,4 @@ export {
175
175
  McpAdapter,
176
176
  mapToolToAction
177
177
  };
178
- //# sourceMappingURL=mcpAdapter-R47GX2P3.js.map
178
+ //# sourceMappingURL=mcpAdapter-WYAXUE7T.js.map
@@ -1,15 +1,19 @@
1
1
  import {
2
2
  LOCKED_ACTIONABLE_TYPES,
3
+ checkSaneNumber,
3
4
  loadPolicy,
4
5
  loadPolicyFromString,
5
6
  policyToConfig,
6
- policyToRole
7
- } from "./chunk-WLIDSTS4.js";
7
+ policyToRole,
8
+ suggestKey
9
+ } from "./chunk-KWZ7JKKO.js";
8
10
  export {
9
11
  LOCKED_ACTIONABLE_TYPES,
12
+ checkSaneNumber,
10
13
  loadPolicy,
11
14
  loadPolicyFromString,
12
15
  policyToConfig,
13
- policyToRole
16
+ policyToRole,
17
+ suggestKey
14
18
  };
15
- //# sourceMappingURL=policyLoader-KZL2U4M2.js.map
19
+ //# sourceMappingURL=policyLoader-XX6BQXNB.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tuent/sentinel",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "AI agent behavioral security monitoring SDK",
5
5
  "author": "Tuent LLC",
6
6
  "keywords": [
@@ -1,10 +0,0 @@
1
- import {
2
- Sentinel
3
- } from "./chunk-GRN5P3H2.js";
4
- import "./chunk-QIYQWOLO.js";
5
- import "./chunk-WLIDSTS4.js";
6
- import "./chunk-NUXSUSYY.js";
7
- export {
8
- Sentinel
9
- };
10
- //# sourceMappingURL=Sentinel-XMSJE4DZ.js.map