@tuent/sentinel 0.1.1 → 0.1.3

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.
@@ -210,6 +210,16 @@ function validatePolicy(data) {
210
210
  }
211
211
  }
212
212
  }
213
+ if (enforcement.unknownTools !== void 0) {
214
+ if (enforcement.unknownTools !== "deny" && enforcement.unknownTools !== "warn") {
215
+ fail('enforcement.unknownTools must be "deny" or "warn"');
216
+ }
217
+ }
218
+ if (enforcement.allowUnknownTools !== void 0) {
219
+ if (!Array.isArray(enforcement.allowUnknownTools) || !enforcement.allowUnknownTools.every((t) => typeof t === "string")) {
220
+ fail("enforcement.allowUnknownTools must be an array of tool name strings");
221
+ }
222
+ }
213
223
  if (enforcement.baselineMaturity !== void 0) {
214
224
  if (typeof enforcement.baselineMaturity !== "object" || enforcement.baselineMaturity === null) {
215
225
  fail("enforcement.baselineMaturity must be an object");
@@ -371,7 +381,7 @@ function policyToRole(policy) {
371
381
  function policyToConfig(policy) {
372
382
  const config = {};
373
383
  if (policy.enforcement) {
374
- if (policy.enforcement.restrictAfter !== void 0 || policy.enforcement.quarantineAfter !== void 0 || policy.enforcement.promote !== void 0 || policy.enforcement.baselineMaturity !== void 0) {
384
+ if (policy.enforcement.restrictAfter !== void 0 || policy.enforcement.quarantineAfter !== void 0 || policy.enforcement.promote !== void 0 || policy.enforcement.baselineMaturity !== void 0 || policy.enforcement.unknownTools !== void 0 || policy.enforcement.allowUnknownTools !== void 0) {
375
385
  config.enforcement = {};
376
386
  if (policy.enforcement.restrictAfter !== void 0) {
377
387
  config.enforcement.restrictAfter = policy.enforcement.restrictAfter;
@@ -385,6 +395,12 @@ function policyToConfig(policy) {
385
395
  if (policy.enforcement.baselineMaturity !== void 0) {
386
396
  config.enforcement.baselineMaturity = policy.enforcement.baselineMaturity;
387
397
  }
398
+ if (policy.enforcement.unknownTools !== void 0) {
399
+ config.enforcement.unknownTools = policy.enforcement.unknownTools;
400
+ }
401
+ if (policy.enforcement.allowUnknownTools !== void 0) {
402
+ config.enforcement.allowUnknownTools = policy.enforcement.allowUnknownTools;
403
+ }
388
404
  }
389
405
  }
390
406
  if (policy.alerts) {
@@ -425,4 +441,4 @@ export {
425
441
  policyToRole,
426
442
  policyToConfig
427
443
  };
428
- //# sourceMappingURL=chunk-2FFMYSVC.js.map
444
+ //# sourceMappingURL=chunk-WLIDSTS4.js.map
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import{runInitClaudeCode as de}from"./chunk-WPTJBRX5.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-NS6ZLMDK.js";import{readReleaseToken as we}from"./chunk-LATQNIRW.js";import{deriveAgentId as $e}from"./chunk-B5QKJHSV.js";import"./chunk-FMZWHT4M.js";import"./chunk-QHE56MEO.js";import{loadPolicy as ve}from"./chunk-2FFMYSVC.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 H,AlertManager as de,AuditTrail as A,BaselineBuilder as N,CorrelationDetector as ge,DeviationDetector as B,FileStorageBackend as ue,ProfileStore as fe,ReportGenerator as pe,Sentinel as R,SentinelRunner as me,generateFleetReport as he}from"./chunk-SSDIBY52.js";import{runInitClaudeCode as ye}from"./chunk-2TJ5Z53T.js";import{readReleaseToken as we}from"./chunk-LATQNIRW.js";import{deriveAgentId as $e}from"./chunk-B5QKJHSV.js";import"./chunk-FMZWHT4M.js";import"./chunk-JTR2E7RD.js";import{loadPolicy as ve}from"./chunk-WLIDSTS4.js";import{getOrCreateKeyPair as M}from"./chunk-NUXSUSYY.js";import{join as u}from"path";import{homedir as m}from"os";import{readFile as j,writeFile as C,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 s=Math.floor(r/24);if(s<14)return`${s} day${s===1?"":"s"} ago`;const d=Math.floor(s/7);if(s<60)return`${d} week${d===1?"":"s"} ago`;if(s>=365)return"over a year ago";const c=Math.floor(s/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 s=Ae(t.lastActive),d=t.weight!=null?Ie(t.weight,t.lastActive):"stable";if(n.push(` Temporal: Last active ${s} | 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(i=>o.get(i)??De(i));n.push(` Connections: ${c.join(", ")}`)}if(t.files&&t.files.length>0){const c=t.files.slice(0,10).map(i=>i.split("/").pop()??i);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(i=>` ${i}`).join(`
5
5
  `))}n.push("")}return n.join(`
6
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(`
7
7
 
@@ -14,20 +14,20 @@ ${G(e)}`}function Ee(e){const o="=== Security Findings ===";if(e.length===0)retu
14
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
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
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}
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"),b=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 T(e,o){try{const n=await j(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(i=>[i.agentId,String(i.score),i.status,i.mode,i.findings,i.lastEvent,i.baseline?"\u2713":"\u2717"]),t=[o,...n],a=o.map((i,l)=>Math.max(...t.map(g=>g[l].length))),r=(i,l)=>i+" ".repeat(l-i.length),s=o.map((i,l)=>r(i,a[l])).join(" "),d=a.map(i=>"-".repeat(i)).join(" "),c=n.map(i=>i.map((l,g)=>r(l,a[g])).join(" "));return[s,d,...c].join(`
18
+ `)}function te(e){const o=["Agent ID","Mode","Reason","Changed"],n=e.map(i=>[i.agentId,i.mode.toUpperCase(),i.reason,i.changed]),t=[o,...n],a=o.map((i,l)=>Math.max(...t.map(g=>g[l].length))),r=(i,l)=>i+" ".repeat(l-i.length),s=o.map((i,l)=>r(i,a[l])).join(" "),d=a.map(i=>"-".repeat(i)).join(" "),c=n.map(i=>i.map((l,g)=>r(l,a[g])).join(" "));return[s,d,...c].join(`
19
+ `)}function ne(e){const o=["Agent ID","Sessions","Last Active","Role","Baseline","Mode","Status"],n=e.map(i=>[i.agentId,String(i.sessions),i.lastActive,i.roleDefined?"\u2713":"\u2717",i.baselineDefined?"\u2713":"\u2717",i.mode??"normal",i.status]),t=[o,...n],a=o.map((i,l)=>Math.max(...t.map(g=>g[l].length))),r=(i,l)=>i+" ".repeat(l-i.length),s=o.map((i,l)=>r(i,a[l])).join(" "),d=a.map(i=>"-".repeat(i)).join(" "),c=n.map(i=>i.map((l,g)=>r(l,a[g])).join(" "));return[s,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(),s=a.getFindings(),d=s.filter(y=>y.severity==="HIGH"||y.severity==="CRITICAL").length,c=s.filter(y=>y.severity==="MEDIUM").length;let i=`${s.length} findings`;d>0?i=`${d} HIGH`:c>0&&(i=`${c} MEDIUM`);const l=P(a.sessionCount,s),g=t.padEnd(18),h=r.padEnd(5);n.push(`[${g}] ${h}| ${a.eventCount} events | ${a.sessionCount} sessions | ${i} | ${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 s={};for(const c of a)s[c.severity]=(s[c.severity]??0)+1;const d=Object.entries(s).map(([c,i])=>`${i} ${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 T(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 C(u(n,e,"mode.json"),JSON.stringify({mode:"restricted",reason:o,timestamp:new Date().toISOString(),previousMode:a}),"utf-8");const r=await M(u(n,e)),s=new A(e,{logDir:u(n,e)});await s.open(),s.setSigningKey(r.privateKey,r.publicKey),await s.logModeChange("restricted",o,a),await s.close(),console.log(`Agent ${e} RESTRICTED: ${o}`)}async function nt(e,o){const n=u(m(),".dahlia","agents"),a=(await T(n,e)).mode;await W(u(n,e),{recursive:!0}),await C(u(n,e,"mode.json"),JSON.stringify({mode:"quarantined",reason:o,timestamp:new Date().toISOString(),previousMode:a}),"utf-8");const r=await M(u(n,e)),s=new A(e,{logDir:u(n,e)});await s.open(),s.setSigningKey(r.privateKey,r.publicKey),await s.logModeChange("quarantined",o,a),await s.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)"}),s=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",i=>c+=i),d.on("end",()=>{try{t({status:d.statusCode??0,body:JSON.parse(c)})}catch{t({status:d.statusCode??0,body:c})}})});s.on("error",a),s.setTimeout(3e3,()=>s.destroy(new Error("timeout"))),s.write(r),s.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 i=await at(a.port,a.token,t);if(i.status===200&&typeof i.body=="object"&&i.body.ok){console.log(se({id:t,selfDerived:n,previousMode:String(i.body.previousMode),mode:String(i.body.mode)}));return}const l=typeof i.body=="object"?JSON.stringify(i.body):i.body;console.error(`[live] daemon refused release (HTTP ${i.status}): ${l}`);return}catch(i){console.error(`[live] daemon release call failed: ${i.message}. Falling back to mode.json.`)}const r=await T(o,t);if(r.mode==="normal"){console.log(`Agent ${t} is already in normal mode.`);return}const s=r.mode;await C(u(o,t,"mode.json"),JSON.stringify({mode:"normal",reason:"manual release",timestamp:new Date().toISOString(),previousMode:s}),"utf-8");const d=await M(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",s),await c.close(),console.log(`[fallback] Agent ${t} RELEASED (was ${s}) \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 T(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 s=(await S.loadAgentProfile(t)).build().filter(I=>I.source==="agent-monitor"),d=s.length,c=s.length>0?s.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(s,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"),E=await T(y,t);o.push({agentId:t,sessions:d,lastActive:c,roleDefined:g!==null,baselineDefined:l!==null,status:P(d,h),mode:E.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),s=o.build().filter(D=>D.source==="agent-monitor"),d=O(s,7),c=d.map(oe),i=[];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};i.push(...D.analyzeSession(ce))}}const l=u(m(),".dahlia","profile.json"),g=new ue(l),h=new fe({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:i,t:y}),k=new Date().toISOString().split("T")[0],$=`Sentinel Agent Report \u2014 ${e} \u2014 ${d.length} recent sessions \u2014 generated ${k}
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
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,s]of Object.entries(a.actionDistribution))console.log(` ${r}: ${s}%`);console.log(` Saved to: ~/.dahlia/agents/${e}/baseline.json`)}async function dt(e,o,n){let t;if(n){let a;try{a=await j(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 C(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 j(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 s;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}};s=new de(l)}for(const l of n)try{const g=new me(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}`)}),s&&g.setAlertManager(s),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)},i=()=>{c().catch(l=>{console.error("Error during shutdown:",l),process.exit(1)})};process.on("SIGINT",i),process.on("SIGTERM",i)}async function ft(e,o,n){const a=await new pe(e).generateReport({periodDays:o,format:n});process.stdout.write(a)}async function pt(e,o){const n=await he({periodDays:e,format:o});process.stdout.write(n)}async function mt(e=30){const o=await q();if(!o){console.log("No sentinel config found. Create ~/.dahlia/sentinel.json or use --init-config.");return}const n=u(m(),".dahlia","agents"),t=new Map;try{for(const s of o.agents){const d=new A(s.agentId,{logDir:u(n,s.agentId)});await d.open(),t.set(s.agentId,d)}const r=await new ge().detect(t,e*24*60*60*1e3);if(r.length===0){console.log("No cross-agent correlations detected.");return}console.log(`Found ${r.length} cross-agent correlation(s):
30
+ `);for(const s of r)console.log(`[${s.severity}] ${s.rule}`),console.log(` Agent A: ${s.agentA.agentId} \u2014 ${s.agentA.action} on ${s.agentA.target}`),console.log(` Agent B: ${s.agentB.agentId} \u2014 ${s.agentB.action} on ${s.agentB.target}`),console.log(` Time delta: ${Math.round(s.timeDeltaMs/1e3)}s`),console.log(` Recommendation: ${s.recommendation}`),console.log("")}finally{for(const a of t.values())await a.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 j(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 M(n),a=new A(e,{logDir:n});a.setSigningKey(t.privateKey,t.publicKey);const r=F&&x?[{file:F,reason:x}]:void 0,s=await a.enrollManifest(r?{quarantine:r}:void 0);console.log(`Manifest enrolled for ${e}: ${s.records} file record(s) signed + chained`+(s.quarantined?`, ${s.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 s of r.issues)console.log(` [${s.type}] ${s.detail}`);for(const s of r.quarantined)console.log(` [QUARANTINED] ${s.file} \u2014 ${s.reason} (retained on disk, excluded from verdict)`)}const a=await n.query({type:"finding",limit:1e4});if(a.length>0){let r=0,s=0,d=0;for(const i of a){const l=i.decision;l==="deny"?s++:l==="modify"?d++:r++}console.log(` Findings: ${a.length} total \u2014 ${r} allowed, ${s} denied, ${d} modified`);const c=a.slice(0,5);for(const i of c){const l=i,g=l.decision,h=l.modification,y=l.description,E=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}`:E?` [allowed (legacy)] ${y}`:` [allowed] ${y}`)}}await n.close()}async function $t(){const e=u(m(),".dahlia","agents"),o=new R({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 s=await o.getHealthScore(r);a.push({agentId:r,score:s.score,status:s.status,mode:s.mode,findings:`${s.findings.critical}/${s.findings.high}/${s.findings.medium}/${s.findings.low}`,lastEvent:s.lastEvent?s.lastEvent.split("T")[0]:"-",baseline:s.baselineEstablished})}await o.stop(),console.log(`Agent Health Dashboard
31
31
  `),console.log(ee(a))}var ie=`# Sentinel \u2014 Agent Security Policy
32
32
  version: "1.0"
33
33
 
@@ -72,9 +72,9 @@ policy:
72
72
  # repoRoot: . # scan this directory for sensitive files
73
73
  # mapPath: ~/.dahlia/repo-sensitivity.json
74
74
  # 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();
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 C(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 R.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 R({agentsDir:t});await a.addAgent(e,e);const r=z?z.split(","):void 0,s=Y?Y.split(",").map(c=>c.trim()):void 0,d=a.startTask(e,o,n,{relaxedActions:r,phases:s});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 R({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 R({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(s=>`${s.action}:${s.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"}),s=(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 i=c.data;return i&&typeof i=="object"&&"aligned"in i&&!i.aligned}).slice(-5);if(d.length>0){console.log(`
78
+ Last ${d.length} misaligned action(s):`);for(const c of d){const i=c.data,l=typeof i.score=="number"?i.score.toFixed(2):"?",g=i.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(s.length>0){console.log(`
79
+ Recent drift findings: ${s.length}`);for(const c of s.slice(-3)){const i=c.data,l=i.severity??"?",g=i.description??"";console.log(` [${l}] ${typeof g=="string"?g.slice(0,80):g}`)}}await n.close()}async function Dt(e,o){try{const n=await ye({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=b?parseInt(b,10):30;await pt(e,Z==="json"?"json":"markdown")}else if(He&&p){const e=b?parseInt(b,10):30;await ft(p,e,Z==="json"?"json":"markdown")}else if(Ke){const e=b?parseInt(b,10):30;await mt(e)}else p?await lt(p):await rt();
@@ -1,4 +1,4 @@
1
- import { v as Sentinel, e as AgentRole, S as SecurityFinding } from '../Sentinel-B_sv8Kiy.js';
1
+ import { v as Sentinel, e as AgentRole, S as SecurityFinding } from '../Sentinel-BVoMEF3F.js';
2
2
  import 'node:crypto';
3
3
 
4
4
  /**
@@ -55,6 +55,33 @@ interface SentinelGatewayOptions {
55
55
  * is disabled (503) — there is no unauthenticated release path.
56
56
  */
57
57
  releaseToken?: string;
58
+ /**
59
+ * Sprint 26 Gate-A Item D (F-8) — disposition for tool names outside the
60
+ * recognized native set (MCP-shaped names never reach this; they have their
61
+ * own translation path). "warn" (default) allows the call and persists an
62
+ * observational unknown_tool finding to the audit trail — no unknown tool
63
+ * passes unlogged; "deny" is the hardened opt-in that blocks the call with
64
+ * a non-escalation-eligible unknown_tool finding. warn ships as the default
65
+ * because the recognized set is inventoried, not provably complete — the
66
+ * default flips to deny after warn-mode dogfooding reconciles the inventory
67
+ * against live traffic. Operator-only: sourced from the launch --policy
68
+ * yaml; workspace yamls contribute roles via the ceiling merge, never
69
+ * gateway config.
70
+ */
71
+ unknownTools?: "deny" | "warn";
72
+ /**
73
+ * Item D escape hatch: native-shaped names to treat as known
74
+ * tool_invocation (e.g. a cc tool newer than this Sentinel build).
75
+ * Operator-only, same channel as unknownTools.
76
+ */
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;
58
85
  }
59
86
  declare class SentinelGateway {
60
87
  private readonly configuredPort;
@@ -68,6 +95,11 @@ declare class SentinelGateway {
68
95
  private readonly operatorCeiling;
69
96
  private readonly home;
70
97
  private readonly releaseToken;
98
+ /** Item D (F-8): disposition for unknown (non-MCP, unrecognized) tool names. */
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;
71
103
  private server;
72
104
  private running;
73
105
  private signalHandlersInstalled;
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  SentinelGateway
3
- } from "../chunk-IYC5E7RL.js";
3
+ } from "../chunk-G74MMDKA.js";
4
4
  import "../chunk-B5QKJHSV.js";
5
5
  import "../chunk-FMZWHT4M.js";
6
- import "../chunk-QHE56MEO.js";
7
- import "../chunk-2FFMYSVC.js";
6
+ import "../chunk-JTR2E7RD.js";
7
+ import "../chunk-WLIDSTS4.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-IYC5E7RL.js";
4
+ } from "./chunk-G74MMDKA.js";
5
+ import {
6
+ computeBuildId,
7
+ runSessionStart
8
+ } from "./chunk-2TJ5Z53T.js";
9
+ import "./chunk-LATQNIRW.js";
5
10
  import "./chunk-B5QKJHSV.js";
6
11
  import "./chunk-FMZWHT4M.js";
7
- import "./chunk-QHE56MEO.js";
8
- import "./chunk-2FFMYSVC.js";
12
+ import "./chunk-JTR2E7RD.js";
13
+ import "./chunk-WLIDSTS4.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-B_sv8Kiy.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-B_sv8Kiy.js';
1
+ import { A as AgentActivityEvent, S as SecurityFinding } from './Sentinel-BVoMEF3F.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-BVoMEF3F.js';
3
3
  import 'node:crypto';
4
4
 
5
5
  interface SentinelPolicy {
@@ -57,6 +57,15 @@ interface SentinelPolicy {
57
57
  minDaysObserved?: number;
58
58
  minCategoryDiversity?: number;
59
59
  };
60
+ /**
61
+ * Item D (F-8): disposition for tool names outside the recognized native
62
+ * set. Default "warn" (allow + persisted observational finding); "deny"
63
+ * is the hardened opt-in. Operator launch policy only — the gateway reads
64
+ * it once at start.
65
+ */
66
+ unknownTools?: "deny" | "warn";
67
+ /** Item D escape hatch: native-shaped names to treat as known. */
68
+ allowUnknownTools?: string[];
60
69
  };
61
70
  alerts?: {
62
71
  channels: (string | {
@@ -119,23 +128,45 @@ declare function runInitClaudeCode(options: {
119
128
  declare function discoverPolicy(startDir: string, home: string): string | null;
120
129
 
121
130
  /**
122
- * `session-start` entry point — called by the hook script on SessionStart.
131
+ * `session-start` entry point — called by the hook script on SessionStart, and
132
+ * the single source of truth for the gateway lifecycle decision (anti-drift: the
133
+ * cc hook delegates here via the dual-mode daemon entry rather than carrying its
134
+ * own copy of this logic).
123
135
  *
124
- * 1. Discover .sentinel.yaml (walk up from cwd to $HOME)
125
- * 2. Acquire gateway lock (PID file check)
126
- * 3. If gateway already running exit early
127
- * 4. If no policy found → exit early (no gateway needed)
128
- * 5. Spawn gateway detached, write PID file, unref
136
+ * Flow:
137
+ * 1. Discover .sentinel.yaml (walk up from cwd to $HOME); none → no gateway.
138
+ * 2. Resolve the daemon entry AT LAUNCH (not a baked path) and hash it — the
139
+ * current build identity.
140
+ * 3. Liveness: is a daemon already running (PID file)?
141
+ * - none → cold spawn (today's path).
142
+ * - running → GET /health, compare its build id to the current entry hash:
143
+ * match → reuse (unchanged fast path).
144
+ * mismatch → RELAUNCH: SIGTERM (→ SIGKILL fallback) the stale daemon,
145
+ * spawn the current build, wait ready. This is pure process
146
+ * replacement: it writes NO mode_change / release / trail
147
+ * state — the new daemon rebuilds mode (mode.json) and count
148
+ * (the append-only trail) lazily via _initWorkspaceAgent, so
149
+ * a restricted/quarantined agent stays restricted/quarantined
150
+ * with the same escalation count across the relaunch.
151
+ * Concurrency: a relaunch holds an atomic lock so two simultaneous session-
152
+ * starts converge on ONE daemon (the loser waits for ready and reuses) instead
153
+ * of racing two daemons onto the port.
129
154
  */
130
155
  interface SessionStartResult {
131
- action: "reused" | "spawned" | "no-policy";
156
+ action: "reused" | "spawned" | "relaunched" | "no-policy";
132
157
  pid?: number;
133
158
  policyPath?: string;
159
+ /** Why a relaunch happened (build mismatch) or null — for telemetry/tests. */
160
+ relaunchReason?: string | null;
134
161
  }
135
162
  declare function runSessionStart(options?: {
136
163
  cwd?: string;
137
164
  home?: string;
138
165
  port?: number;
166
+ /** Daemon entry to launch. Defaults to resolveGatewayEntryPoint() (resolved at
167
+ * launch, NOT a baked path — closes the pnpm-relocation / dangling cases). The
168
+ * dual-mode entry passes its own path so the launcher spawns the current build. */
169
+ gatewayEntry?: string;
139
170
  }): Promise<SessionStartResult>;
140
171
 
141
172
  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-WPTJBRX5.js";
1
+ import "./chunk-TKAKHSZ3.js";
5
2
  import {
6
3
  Sentinel,
7
4
  createCliApproval
8
- } from "./chunk-NS6ZLMDK.js";
5
+ } from "./chunk-SSDIBY52.js";
6
+ import {
7
+ runInitClaudeCode,
8
+ runSessionStart
9
+ } from "./chunk-2TJ5Z53T.js";
9
10
  import "./chunk-LATQNIRW.js";
10
11
  import {
11
12
  discoverPolicy
12
13
  } from "./chunk-FMZWHT4M.js";
13
- import "./chunk-QHE56MEO.js";
14
+ import "./chunk-JTR2E7RD.js";
14
15
  import {
15
16
  loadPolicy,
16
17
  loadPolicyFromString
17
- } from "./chunk-2FFMYSVC.js";
18
+ } from "./chunk-WLIDSTS4.js";
18
19
  import "./chunk-NUXSUSYY.js";
19
20
  export {
20
21
  Sentinel,
@@ -4,7 +4,7 @@ import {
4
4
  loadPolicyFromString,
5
5
  policyToConfig,
6
6
  policyToRole
7
- } from "./chunk-2FFMYSVC.js";
7
+ } from "./chunk-WLIDSTS4.js";
8
8
  export {
9
9
  LOCKED_ACTIONABLE_TYPES,
10
10
  loadPolicy,
@@ -12,4 +12,4 @@ export {
12
12
  policyToConfig,
13
13
  policyToRole
14
14
  };
15
- //# sourceMappingURL=policyLoader-6KR5VFVV.js.map
15
+ //# sourceMappingURL=policyLoader-KZL2U4M2.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tuent/sentinel",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
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-NS6ZLMDK.js";
4
- import "./chunk-QHE56MEO.js";
5
- import "./chunk-2FFMYSVC.js";
6
- import "./chunk-NUXSUSYY.js";
7
- export {
8
- Sentinel
9
- };
10
- //# sourceMappingURL=Sentinel-QHMQ67W3.js.map