@pithosai/pithosai 1.0.10 → 1.0.33
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/CHANGELOG.md +784 -0
- package/LICENSE +1 -1
- package/README.md +402 -31
- package/dist/529.mjs +2 -0
- package/dist/813.mjs +3 -0
- package/dist/heartbeat.mjs +366 -118
- package/dist/init.mjs +1 -1
- package/dist/nl-fallback-classify.mjs +7 -0
- package/dist/pithosai-git-ai-adapter.mjs +2 -0
- package/dist/pithosai-nl-fallback.sh +456 -0
- package/dist/pithosai.mjs +366 -118
- package/dist/render-markdown.mjs +7 -7
- package/docs/KROUTER.md +140 -0
- package/docs/hooks-usage.md +335 -0
- package/package.json +26 -8
- package/src/agent-orch/READEM.md +62 -0
- package/src/models/README.md +469 -0
- package/src/models/krouter.README.md +63 -0
- package/dist/935.mjs +0 -3
package/dist/render-markdown.mjs
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import{createRequire as e}from"node:module";let t=e(import.meta.url);var r,n,a,i,s,l,o,c={5590(e){e.exports=import("chalk").then(function(e){return e})},8703(e){e.exports=import("marked").then(function(e){return e})},1421(e){e.exports=t("node:child_process")},1455(e){e.exports=t("node:fs/promises")},6760(e){e.exports=t("node:path")},6885(e,t,r){r.a(e,async function(e,t){try{var n=r(1421),a=r(1455),i=r(6760),s=r(161),l=e([s]);async function o({text:e,showAll:t}){let r,a=e.endsWith("\n")?e:`${e}
|
|
3
3
|
`;if(t||!process.stdout.isTTY)return void process.stdout.write(a);let i=(r=process.env.PAGER?.trim())||"less -RFX",s=(0,n.spawn)(i,{shell:!0,stdio:["pipe","inherit","inherit"],env:process.env});await new Promise(e=>{let t=!1,r=()=>{t||(t=!0,e())};s.once("error",e=>{process.stderr.write(`pager failed (${e.message}); printing full output to stdout
|
|
4
4
|
`),process.stdout.write(a),r()}),s.once("close",()=>r());let n=s.stdin;if(!n){process.stdout.write(a),r();return}new Promise((e,t)=>{n.end(a,"utf8",r=>{r?t(r):e()})}).catch(e=>{process.stderr.write(`pager write failed (${e.message}); printing full output to stdout
|
|
5
|
-
`),process.stdout.write(a),r()})})}async function c(){var e;let t,r,n=(t=(e=process.argv.slice(2)).includes("--show-all"),r=e.filter(e=>"--show-all"!==e),0===r.length?{ok:!1,message:"Usage: node scripts/render-markdown.js [--show-all] <path-to-file.md>"}:r.length>1?{ok:!1,message:"Expected a single markdown file path."}:{ok:!0,filePath:r[0],showAll:t});n.ok||(console.error(n.message),process.exit(1));let{filePath:l,showAll:c}=n,
|
|
5
|
+
`),process.stdout.write(a),r()})})}async function c(){var e;let t,r,n=(t=(e=process.argv.slice(2)).includes("--show-all"),r=e.filter(e=>"--show-all"!==e),0===r.length?{ok:!1,message:"Usage: node scripts/render-markdown.js [--show-all] <path-to-file.md>"}:r.length>1?{ok:!1,message:"Expected a single markdown file path."}:{ok:!0,filePath:r[0],showAll:t});n.ok||(console.error(n.message),process.exit(1));let{filePath:l,showAll:c}=n,u=(0,i.resolve)(process.cwd(),l);try{let e=await (0,a.readFile)(u,"utf-8"),t=(0,s.oq)(e);await o({text:t,showAll:c})}catch(e){console.error(`Error: ${e.message}`),process.exit(1)}}s=(l.then?(await l)():l)[0],function(e=process.argv[1]){if(!e)return!1;let t=(0,i.basename)((0,i.resolve)(e));return"render-markdown"===t||"render-markdown.js"===t||"render-markdown.mjs"===t}()&&c(),t()}catch(e){t(e)}})},5935(e,t,r){r.d(t,{$0:()=>s,dE:()=>i});let n=RegExp("\\x1b\\[[0-9;]*m","g"),a=RegExp("\\x1b\\]8;;.*?\\x1b\\\\|\\x1b\\]8;;\\x1b\\\\","g");function i(e){return 0===e||e<32||e>=127&&e<160||8205===e||8204===e||e>=65024&&e<=65039||e>=917760&&e<=917999||e>=768&&e<=879||e>=6832&&e<=6911||e>=7616&&e<=7679||e>=8400&&e<=8447||e>=65056&&e<=65071?0:e>=127744&&e<=129791||e>=9728&&e<=10175||e>=65280&&e<=65519?2:e<4352?e>=12288&&e<=12351?2:1:e>=4352&&e<=4607||e>=11904&&e<=42191||e>=44032&&e<=55215||e>=63744&&e<=64255||e>=65040&&e<=65135||e>=131072&&e<=196605||e>=196608&&e<=262141?2:1}function s(e){let t=e.replace(a,"").replace(n,""),r=0;for(let e=0;e<t.length;){let n=t.codePointAt(e)??0;r+=i(n),e+=n>65535?2:1}return r}},161(e,t,r){r.a(e,async function(e,n){try{r.d(t,{oq:()=>c});var a=r(8703),i=r(1483),s=r(6022),l=e([i,a]);[i,a]=l.then?(await l)():l;let u="\x1b]8;;",p=!1;function o(e){let t=String(e??"");return!(!/^-/.test(t)&&!/\|/.test(t)||/[A-Za-z0-9]/.test(t))&&/-/.test(t)}function c(e){if("string"!=typeof e||!e.trim())return e??"";p||(a.marked.use({renderer:{heading({tokens:e,depth:t}){let r=this.parser.parseInline(e),n=i.w4.heading(r);return 1===t?`
|
|
6
6
|
${n}
|
|
7
7
|
`:`${n}
|
|
8
|
-
`},strong({tokens:e}){return i.w4.strong(this.parser.parseInline(e))},em({tokens:e}){return i.w4.muted(this.parser.parseInline(e))},codespan:({text:e})=>i.w4.muted(e),link({tokens:e,href:t}){var r,n;return r=this.parser.parseInline(e),n=t??"",n?.trim()?(0,i.D8)()?`${
|
|
8
|
+
`},strong({tokens:e}){return i.w4.strong(this.parser.parseInline(e))},em({tokens:e}){return i.w4.muted(this.parser.parseInline(e))},codespan:({text:e})=>i.w4.muted(e),link({tokens:e,href:t}){var r,n;return r=this.parser.parseInline(e),n=t??"",n?.trim()?(0,i.D8)()?`${u}${n}\x1b\\${r}${u}\x1b\\`:`${r} (${n})`:r},blockquote({tokens:e}){let t=this.parser.parse(e).trimEnd().split("\n").map(e=>i.w4.muted("│ ")+e).join("\n");return`
|
|
9
9
|
${t}
|
|
10
10
|
|
|
11
11
|
`},hr(){let e=Math.min(process.stdout?.columns??80,80)-2;return"\n"+i.w4.muted("─".repeat(Math.max(0,e)))+"\n"},code({text:e,lang:t}){let r=(t??"").trim().toLowerCase(),n=/^(bash|sh|shell|zsh)$/.test(r),a=/^(javascript|js|typescript|ts|python|py|java|rust|rs|c|cpp|c\+\+)$/.test(r),s=n?i.w4.command:a?i.w4.info:i.w4.muted,l=t?i.w4.muted(`[${t}] `):"",o=e.replace(/\n$/,"").split("\n").map(e=>s(e)).join("\n");return`
|
|
@@ -16,8 +16,8 @@ ${r}
|
|
|
16
16
|
|
|
17
17
|
`},listitem({tokens:e,task:t,checked:r}){return(t?i.w4[r?"success":"muted"](r?"[x] ":"[ ] "):"")+this.parser.parse(e)},paragraph({tokens:e}){return this.parser.parseInline(e)+"\n\n"},table(e){let t=e?.header??[],r=e?.rows??[],n=e?.align,a=e=>e?null!=e.tokens?this.parser.parseInline(e.tokens):"string"==typeof e.text?e.text:String(e):"";if(!Array.isArray(t)||!Array.isArray(r)||0===t.length){let e=[t,...r].filter(Array.isArray).map(e=>e.map(a).join(" | ")).join("\n");return e?`
|
|
18
18
|
${i.w4.muted(e)}
|
|
19
|
-
`:"\n"}let l=t.length,o=t.map(a),c={left:"left",center:"center",right:"right"},
|
|
20
|
-
${
|
|
21
|
-
`},tablerow:({text:e})=>e,tablecell({tokens:e}){return this.parser.parseInline(e)},br:()=>"\n",text(e){return e.tokens&&e.tokens.length>0?this.parser.parseInline(e.tokens):e.text??""},html:()=>"",checkbox:({checked:e})=>i.w4[e?"success":"muted"](e?"[x]":"[ ]")+" "}}),a.marked.setOptions({gfm:!0,breaks:!0}),p=!0);let t="string"!=typeof e
|
|
22
|
-
`}let i=Math.max(0,e.padding??1),s=e.columns,l=s.map(e=>({headerW:(0,n.$0)(e.header),cellW:Math.max(0,...t.map(t=>(0,n.$0)(t[e.key]??"")))})),o=s.map((e,t)=>{let r=l[t],n=Math.max(r?.headerW??0,r?.cellW??0)+2*i,a=e.maxWidth?Math.min(n,e.maxWidth):n;return Math.max(e.minWidth??3,a)}),c=function(e){if(null!=e&&Number.isFinite(e)&&!(e<=0))return Math.floor(e)}(e.width),
|
|
23
|
-
`}},1483(e,t,r){r.a(e,async function(e,n){try{let l,o,c,p,
|
|
19
|
+
`:"\n"}let l=t.length,o=t.map(a),c={left:"left",center:"center",right:"right"},u=o.map((e,t)=>({key:String(t),header:i.w4.heading(e),align:c[n?.[t]]??"left"})),p=r.map(e=>{let t={},r=Array.isArray(e)?e:[];for(let e=0;e<l;e+=1)t[String(e)]=i.w4.muted(a(r[e]));return t}),h=process.stdout?.columns??80,m=u.map(e=>({...e,flex:!0})),f=(0,s.b)({columns:m,rows:p,width:h,padding:1,border:"unicode"});return`
|
|
20
|
+
${f}
|
|
21
|
+
`},tablerow:({text:e})=>e,tablecell({tokens:e}){return this.parser.parseInline(e)},br:()=>"\n",text(e){return e.tokens&&e.tokens.length>0?this.parser.parseInline(e.tokens):e.text??""},html:()=>"",checkbox:({checked:e})=>i.w4[e?"success":"muted"](e?"[x]":"[ ]")+" "}}),a.marked.setOptions({gfm:!0,breaks:!0}),p=!0);let t=function(e){if("string"!=typeof e)return"";let t=e.trim().replace(/\r\n/g,"\n").replace(/\r/g,"\n").replace(/^(\s*\d+\.)\n(?!\s*\d+\.)(\S[^\n]*)/gm,"$1 $2");var r=function(e){let t=String(e).split("\n"),r=[],n=!1;for(let e=0;e<t.length;e++){let a=t[e];if(/^\s*(```|~~~)/.test(a)){n=!n,r.push(a);continue}if(n){r.push(a);continue}for(;e+1<t.length&&function(e){let t=String(e??"").replace(/```[\s\S]*?```/g,""),r=t.match(/`/g);if(r&&r.length%2==1)return!0;let n=t.match(/\*\*/g);if(n&&n.length%2==1)return!0;let a=t.replace(/\*\*/g,"").replace(/^[ \t]*\*[ \t]/gm,"").match(/\*/g);return!!a&&a.length%2==1}(a);){let r=t[e+=1];if(""===r)continue;let n=/[`*_({\[]$/u.test(a)||/^[`*_)\]]/u.test(r)||/\d$/u.test(a)&&/^\d/u.test(r)?"":" ";a=`${a}${n}${r}`}r.push(a)}return r.join("\n")}(t);let n="",a=r;for(;a!==n;)n=a,a=a.replace(/^(\|[^\n]*?)\n(?!\s*\|)(\S[^\n]*\|[^\n]*)$/gm,(e,t,r)=>{let n=String(t).replace(/\s+$/u,""),a=String(r).replace(/^\s+/u,""),i=o(n)||o(a)?"":" ";return`${n}${i}${a}`});return a}(e);try{let e=a.marked.parse(t,{async:!1});return"string"==typeof e?e:String(e)}catch{return e}}n()}catch(e){n(e)}})},9005(e,t,r){r.d(t,{A:()=>n});let n={accent:"#FF5A2D",accentBright:"#FF7A3D",accentDim:"#D14A22",info:"#FF8A5B",success:"#2FBF71",warn:"#FFB020",error:"#E23D2D",muted:"#8B7F77"}},6022(e,t,r){r.d(t,{b:()=>i});var n=r(5935);function a(e,t){return t<=0?"":e.repeat(t)}function i(e){let t=e.rows.map(e=>{let t={};for(let[r,n]of Object.entries(e))t[r]=null==n?"":String(n);return t}),r=e.border??"unicode";if("none"===r){let r=e.columns,n=[r.map(e=>e.header).join(" | "),...t.map(e=>r.map(t=>e[t.key]??"").join(" | "))];return`${n.join("\n")}
|
|
22
|
+
`}let i=Math.max(0,e.padding??1),s=e.columns,l=s.map(e=>({headerW:(0,n.$0)(e.header),cellW:Math.max(0,...t.map(t=>(0,n.$0)(t[e.key]??"")))})),o=s.map((e,t)=>{let r=l[t],n=Math.max(r?.headerW??0,r?.cellW??0)+2*i,a=e.maxWidth?Math.min(n,e.maxWidth):n;return Math.max(e.minWidth??3,a)}),c=function(e){if(null!=e&&Number.isFinite(e)&&!(e<=0))return Math.floor(e)}(e.width),u=s.length+1,p=o.reduce((e,t)=>e+t,0)+u,h=e=>Math.max((l[e]?.headerW??0)+2*i,(l[e]?.cellW??0)+2*i,3),m=s.map((e,t)=>Math.max(e.minWidth??3,h(t))),f=s.map((e,t)=>h(t));if(c&&p>c){let e=p-c,t=s.map((e,t)=>({i:t,w:o[t]??0})).filter(({i:e})=>!!s[e]?.flex).sort((e,t)=>t.w-e.w).map(e=>e.i),r=s.map((e,t)=>({i:t,w:o[t]??0})).filter(({i:e})=>!s[e]?.flex).sort((e,t)=>t.w-e.w).map(e=>e.i),n=(t,r)=>{for(;e>0;){let n=!1;for(let a of t)if(!((o[a]??0)<=(r[a]??0))&&(o[a]=(o[a]??0)-1,n=!0,(e-=1)<=0))break;if(!n)break}};if(n(t,m),n(t,f),n(r,m),n(r,f),e>0){let e=s.map((e,t)=>Math.max(3,e.minWidth??0));n(t,e),n(r,e)}}if(c){let e=s.length+1,t=c-(o.reduce((e,t)=>e+t,0)+e);if(t>0){let e=s.map((e,t)=>({c:e,i:t})).filter(({c:e})=>!!e.flex).map(({i:e})=>e);if(e.length>0){let r=s.map(e=>"number"==typeof e.maxWidth&&e.maxWidth>0?Math.floor(e.maxWidth):1/0);for(;t>0;){let n=!1;for(let a of e)if(!((o[a]??0)>=(r[a]??1/0))&&(o[a]=(o[a]??0)+1,n=!0,(t-=1)<=0))break;if(!n)break}}}}let d="ascii"===r?{tl:"+",tr:"+",bl:"+",br:"+",h:"-",v:"|",t:"+",ml:"+",m:"+",mr:"+",b:"+"}:{tl:"┌",tr:"┐",bl:"└",br:"┘",h:"─",v:"│",t:"┬",ml:"├",m:"┼",mr:"┤",b:"┴"},g=(e,t,r)=>`${e}${o.map(e=>a(d.h,e)).join(t)}${r}`,x=e=>Math.max(1,o[e]-2*i),w=a(" ",i),$=(e,t=!1)=>{let r=s.map(r=>t?r.header:e[r.key]??"").map((e,t)=>(function(e,t){if(t<=0)return[e];let r=[];for(let t=0;t<e.length;){if("\x1b"===e[t]){if("["===e[t+1]){let n=t+2;for(;n<e.length;){let t=e[n];if("m"===t)break;if(t&&t>="0"&&t<="9"||";"===t){n+=1;continue}break}if("m"===e[n]){r.push({kind:"ansi",value:e.slice(t,n+1)}),t=n+1;continue}}if("]"===e[t+1]&&"8;;"===e.slice(t+2,t+5)){let n=e.indexOf("\x1b\\",t+5);if(n>=0){r.push({kind:"ansi",value:e.slice(t,n+2)}),t=n+2;continue}}}let a=e.codePointAt(t)??0,i=String.fromCodePoint(a);r.push({kind:"char",value:i,w:(0,n.dE)(a)}),t+=a>65535?2:1}let a=r.findIndex(e=>"char"===e.kind);if(a<0)return[e];let i=-1;for(let e=r.length-1;e>=0;e-=1)if(r[e]?.kind==="char"){i=e;break}let s=r.slice(0,a).filter(e=>"ansi"===e.kind).map(e=>e.value).join(""),l=r.slice(i+1).filter(e=>"ansi"===e.kind).map(e=>e.value).join(""),o=r.slice(a,i+1),c=[],u=e=>" "===e||" "===e||"/"===e||"-"===e||"_"===e||"."===e,p=e=>" "===e||" "===e,h=[],m=0,f=null,d=e=>(e??h).map(e=>e.value).join(""),g=e=>{let t=e.replace(/\s+$/,"");0!==t.trim().length&&c.push(t)},x=e=>{if(0===h.length)return;if(null==e||e<=0){g(d()),h.length=0,m=0,f=null;return}let t=h.slice(0,e),r=h.slice(e);for(g(d(t));r.length>0&&r[0]?.kind==="char"&&p(r[0].value);)r.shift();h.length=0,h.push(...r),m=r.filter(e=>"char"===e.kind).reduce((e,t)=>e+(t.w??1),0),f=null};for(let e of o){if("ansi"===e.kind){h.push(e);continue}let r=e.value,n=e.w??1;if("\n"===r||"\r"===r){x(h.length);continue}m+n>t&&m>0&&x(f),h.push(e),m+=n,u(r)&&(f=h.length)}return(x(h.length),c.length)?s||l?c.map(e=>e?`${s}${e}${l}`:e):c:[""]})(e,x(t))),i=Math.max(...r.map(e=>e.length)),l=[];for(let e=0;e<i;e+=1){let t=r.map((t,r)=>{let i=function(e,t,r){let i=(0,n.$0)(e);if(i>=t)return e;let s=t-i;if("right"===r)return`${a(" ",s)}${e}`;if("center"===r){let t=Math.floor(s/2),r=s-t;return`${a(" ",t)}${e}${a(" ",r)}`}return`${e}${a(" ",s)}`}(t[e]??"",x(r),s[r]?.align??"left");return`${w}${i}${w}`});l.push(`${d.v}${t.join(d.v)}${d.v}`)}return l},b=[];for(let e of(b.push(g(d.tl,d.t,d.tr)),b.push(...$({},!0)),b.push(g(d.ml,d.m,d.mr)),t))b.push(...$(e,!1));return b.push(g(d.bl,d.b,d.br)),`${b.join("\n")}
|
|
23
|
+
`}},1483(e,t,r){r.a(e,async function(e,n){try{let l,o,c,u,p,h,m,f,d,g;r.d(t,{D8:()=>b,w4:()=>$});var a=r(8691),i=r(9005),s=e([a]);a=(s.then?(await s)():s)[0];let x="string"==typeof process.env.FORCE_COLOR&&process.env.FORCE_COLOR.trim().length>0&&"0"!==process.env.FORCE_COLOR.trim(),w=process.env.NO_COLOR&&!x?new a.g({level:0}):a.m,$={accent:(l=i.A.accent,w.hex(l)),accentBright:(o=i.A.accentBright,w.hex(o)),accentDim:(c=i.A.accentDim,w.hex(c)),info:(u=i.A.info,w.hex(u)),success:(p=i.A.success,w.hex(p)),warn:(h=i.A.warn,w.hex(h)),error:(m=i.A.error,w.hex(m)),muted:(f=i.A.muted,w.hex(f)),heading:w.bold.hex(i.A.accent),strong:w.bold.hex(i.A.accentBright),command:(d=i.A.accentBright,w.hex(d)),option:(g=i.A.warn,w.hex(g))},b=()=>w.level>0;n()}catch(e){n(e)}})},8691(e,t,r){r.a(e,async function(e,n){try{r.d(t,{g:()=>a.Chalk,m:()=>s});var a=r(5590),i=e([a]);a=(i.then?(await i)():i)[0];let s=function(){if("function"==typeof a.default.hex)return a.default;let e=a.default.default;return e&&"function"==typeof e.hex?e:a.default}();n()}catch(e){n(e)}})}},u={};function p(e){var t=u[e];if(void 0!==t)return t.exports;var r=u[e]={exports:{}};return c[e](r,r.exports,p),r.exports}n=(r="function"==typeof Symbol)?Symbol("rspack queues"):"__rspack_queues",a=p.aE=r?Symbol("rspack exports"):"__webpack_exports__",i=r?Symbol("rspack error"):"__rspack_error",s=r?Symbol("rspack done"):"__rspack_done",l=p.zS=r?Symbol("rspack defer"):"__rspack_defer",o=e=>{e&&e.d<1&&(e.d=1,e.forEach(e=>e.r--),e.forEach(e=>e.r--?e.r++:e()))},p.a=(e,t,r)=>{r&&((c=[]).d=-1);var c,h,m,f,d=new Set,g=e.exports,x=new Promise((e,t)=>{f=t,m=e});x[a]=g,x[n]=e=>{c&&e(c),d.forEach(e),x.catch(()=>{})},e.exports=x,t(e=>{h=e.map(e=>{if(null!==e&&"object"==typeof e){if(!e[n]&&e[l]){var t=e[l];if(!t.some(e=>{var t=u[e];return!t||!1===t[s]}))return e;var r=e;e={then(e){Promise.all(t.map(p)).then(()=>e(r))}}}if(e[n])return e;if(e.then){var c=[];c.d=0,e.then(e=>{h[a]=e,o(c)},e=>{h[i]=e,o(c)});var h={};return h[l]=!1,h[n]=e=>e(c),h}}var m={};return m[n]=()=>{},m[a]=e,m});var t,r=()=>h.map(e=>{if(e[l])return e;if(e[i])throw e[i];return e[a]}),m=new Promise(e=>{(t=()=>e(r)).r=0;var a=e=>e!==c&&!d.has(e)&&(d.add(e),e&&!e.d&&(t.r++,e.push(t)));h.map(e=>e[l]||e[n](a))});return t.r?m:r()},e=>(e?f(x[i]=e):m(g),o(c),x[s]=!0)),c&&c.d<0&&(c.d=0)},p.d=(e,t)=>{for(var r in t)p.o(t,r)&&!p.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},p.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),p.rv=()=>"1.7.6",p.ruid="bundler=rspack@1.7.6",p(6885);
|
package/docs/KROUTER.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Install kRouter (`@sifxprime/krouter`)
|
|
2
|
+
|
|
3
|
+
Pithosai talks to **Kiro provider** models through a local [kRouter](https://github.com/sifxprime/krouter) gateway (`@sifxprime/krouter` on npm). Krouter is **on by default** (`http://localhost:20128/v1`). The gateway must be running for `--model krouter-*` requests to succeed. Set `KROUTER_DISABLED=1` to hide curated keys and skip Krouter in the env-walk.
|
|
4
|
+
|
|
5
|
+
Official project docs: [github.com/sifxprime/krouter](https://github.com/sifxprime/krouter), [npmjs.com/package/@sifxprime/krouter](https://www.npmjs.com/package/@sifxprime/krouter), [krouter.kodelyht.com](https://krouter.kodelyht.com).
|
|
6
|
+
|
|
7
|
+
Pithosai CLI keys and model pick guide: [`src/models/krouter.README.md`](../src/models/krouter.README.md).
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- **Node.js ≥ 20** (same floor as pithosai)
|
|
12
|
+
- A provider in the kRouter dashboard (Kiro OAuth for curated `kr/glm-5*`, `kr/minimax-*`, `kr/deepseek-*` keys)
|
|
13
|
+
|
|
14
|
+
## Install and start
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Pin 0.5.118: @latest (0.5.143) crashes — npm tarball omits app/server-peer-patch.js
|
|
18
|
+
npm install -g @sifxprime/krouter@0.5.118
|
|
19
|
+
|
|
20
|
+
# Background (system tray); dashboard at http://localhost:20128/dashboard
|
|
21
|
+
# --skip-update prevents the CLI from upgrading back to 0.5.143
|
|
22
|
+
krouter -t --skip-update
|
|
23
|
+
|
|
24
|
+
# Or foreground with live logs
|
|
25
|
+
krouter --skip-update
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Default listen address is `http://127.0.0.1:20128`. OpenAI-compatible API: `http://localhost:20128/v1`.
|
|
29
|
+
|
|
30
|
+
| Flag | Meaning |
|
|
31
|
+
|------|---------|
|
|
32
|
+
| `-t`, `--tray` | Run in the system tray |
|
|
33
|
+
| `-p`, `--port <port>` | Port (default `20128`) |
|
|
34
|
+
| `-l`, `--log` | Show server logs (hidden by default) |
|
|
35
|
+
| `--skip-update` | Skip the auto-update check |
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
krouter --help
|
|
39
|
+
krouter --version
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Other install paths
|
|
43
|
+
|
|
44
|
+
**From source**
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git clone https://github.com/sifxprime/krouter.git
|
|
48
|
+
cd krouter
|
|
49
|
+
npm install
|
|
50
|
+
npm run dev
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Docker**
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
docker run -d \
|
|
57
|
+
-p 20128:20128 \
|
|
58
|
+
-v "$HOME/.krouter:/app/data" \
|
|
59
|
+
--name krouter \
|
|
60
|
+
sifxprime/krouter:latest
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**PM2**
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm install -g @sifxprime/krouter pm2
|
|
67
|
+
pm2 start krouter --name krouter -- --skip-update
|
|
68
|
+
pm2 save
|
|
69
|
+
pm2 startup
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**VPS (bind all interfaces)**
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install -g @sifxprime/krouter
|
|
76
|
+
export PORT=20128 HOSTNAME=0.0.0.0
|
|
77
|
+
krouter --skip-update
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Put HTTPS in front (Nginx / Caddy / Cloudflare Tunnel). Set `REQUIRE_API_KEY=true` if the `/v1` endpoint is reachable from the network.
|
|
81
|
+
|
|
82
|
+
## Add a provider
|
|
83
|
+
|
|
84
|
+
1. Open [http://localhost:20128/dashboard](http://localhost:20128/dashboard).
|
|
85
|
+
2. **Providers → Add**. For pithosai curated `kr/…` keys, add **Kiro** and complete OAuth.
|
|
86
|
+
3. **Test Connection**.
|
|
87
|
+
|
|
88
|
+
Dashboard combos (`RD` / `ARCH` / `PM` / `QA`) are configured here. Pithosai maps them to `--model krouter-rd` / `krouter-arch` / `krouter-pm` / `krouter-qa`. Other `/v1/models` ids use `--model krouter/<id>`.
|
|
89
|
+
|
|
90
|
+
## Point pithosai at kRouter
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# optional: override the default http://localhost:20128/v1
|
|
94
|
+
export KROUTER_API_BASE=http://localhost:20128/v1
|
|
95
|
+
# opt out: export KROUTER_DISABLED=1
|
|
96
|
+
# optional, if the dashboard requires a key:
|
|
97
|
+
# export KROUTER_API_KEY=sk-krouter-...
|
|
98
|
+
|
|
99
|
+
pithosai --no-interactive --model krouter-rd "Add tests for this file"
|
|
100
|
+
pithosai --no-interactive --model krouter/cu/default "Summarize this module"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Legacy aliases `KIRO_API_BASE` / `KIRO_DISABLED` / `KIRO_API_KEY` still work.
|
|
104
|
+
|
|
105
|
+
Local kRouter with `REQUIRE_API_KEY` unset does not require a key. Pithosai then sends the dummy key `krouter`. If you created a key in **Dashboard → API Keys**, set `KROUTER_API_KEY` to that value.
|
|
106
|
+
|
|
107
|
+
| Pithosai env | Role |
|
|
108
|
+
|--------------|------|
|
|
109
|
+
| `KROUTER_API_BASE` | OpenAI-compatible base URL (default `http://localhost:20128/v1`) |
|
|
110
|
+
| `KROUTER_DISABLED` | `1` / `true` / `yes` to hide curated keys and skip Krouter in the env-walk |
|
|
111
|
+
| `KROUTER_API_KEY` | Bearer token when kRouter auth is on |
|
|
112
|
+
|
|
113
|
+
kRouter process env (`PORT`, `HOSTNAME`, `DATA_DIR`, `REQUIRE_API_KEY`, …) is documented in the [upstream README](https://github.com/sifxprime/krouter#environment-variables).
|
|
114
|
+
|
|
115
|
+
## Check the gateway
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
curl -sS http://localhost:20128/v1/models
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
You should see configured providers and combos. If this fails, pithosai `--model krouter-*` will fail the same way.
|
|
122
|
+
|
|
123
|
+
## Uninstall kRouter
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Stop first: tray → Quit, or `pkill -f krouter` / `docker stop krouter`
|
|
127
|
+
npm uninstall -g @sifxprime/krouter
|
|
128
|
+
rm -rf ~/.krouter # optional: wipe kRouter data
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Troubleshooting
|
|
132
|
+
|
|
133
|
+
| Symptom | What to try |
|
|
134
|
+
|---------|-------------|
|
|
135
|
+
| Connection refused on `20128` | Start `krouter` or `krouter -t`; confirm `PORT` |
|
|
136
|
+
| `No active credentials for provider` | Reconnect Kiro (or another provider) in the dashboard |
|
|
137
|
+
| 401 from `/v1` | Set `KROUTER_API_KEY` to a Dashboard API key, or turn off `REQUIRE_API_KEY` |
|
|
138
|
+
| Curated `kr/…` missing from `/v1/models` | Add the provider in the dashboard; use `krouter/<id>` for whatever `/v1/models` lists; combos `RD`/`ARCH`/`PM`/`QA` are dashboard names |
|
|
139
|
+
|
|
140
|
+
Full upstream troubleshooting: [krouter.kodelyht.com](https://krouter.kodelyht.com).
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# Pithosai hook system
|
|
2
|
+
|
|
3
|
+
Hooks run custom logic at agent lifecycle points: debugging, security, observability, and JSON command hooks (including the shipped Git AI CLI).
|
|
4
|
+
|
|
5
|
+
There are two layers:
|
|
6
|
+
|
|
7
|
+
1. **In-process `HookManager`** — JavaScript handlers registered on pithos core hook types. Built-in plugins and CLI debug/security presets use this.
|
|
8
|
+
2. **JSON command hooks** — `*.json` files that spawn a `command` (or call a registered in-process adapter). Git AI is **not** a core in-process adapter; it is an external CLI spawned from bundled/user JSON.
|
|
9
|
+
|
|
10
|
+
JS constants live on `HookType` in `src/agent/hooks/index.js` (for example `HookType.BEFORE_TOOL_EXECUTE`). Values and JSON `hookType` fields are **camelCase** (`beforeToolExecute`). CLI `hooks test` / `hooks register` require those camelCase values, not `BEFORE_TOOL_EXECUTE`.
|
|
11
|
+
|
|
12
|
+
## Hook types
|
|
13
|
+
|
|
14
|
+
| Value | `HookType` constant | When it fires |
|
|
15
|
+
|-------|---------------------|---------------|
|
|
16
|
+
| `beforeSystemPromptBuild` | `BEFORE_SYSTEM_PROMPT_BUILD` | Before the system prompt is assembled |
|
|
17
|
+
| `afterSystemPromptBuild` | `AFTER_SYSTEM_PROMPT_BUILD` | After the system prompt is assembled |
|
|
18
|
+
| `beforeModelCall` | `BEFORE_MODEL_CALL` | Assistant `message_start` |
|
|
19
|
+
| `afterModelCall` | `AFTER_MODEL_CALL` | Assistant `message_end` |
|
|
20
|
+
| `messageUpdate` | `MESSAGE_UPDATE` | Streaming chunks (`text_delta`, `thinking_delta`). Fire-and-forget so handlers do not block the stream |
|
|
21
|
+
| `beforeToolCall` | `BEFORE_TOOL_CALL` | Agent `tool_call` event (model requested a tool) |
|
|
22
|
+
| `afterToolCall` | `AFTER_TOOL_CALL` | Agent `tool_result` event |
|
|
23
|
+
| `beforeToolExecute` | `BEFORE_TOOL_EXECUTE` | Immediately before `tool.execute`. Can **block** the tool |
|
|
24
|
+
| `afterToolExecute` | `AFTER_TOOL_EXECUTE` | After `tool.execute` (success or thrown error) |
|
|
25
|
+
| `beforeAgentRun` | `BEFORE_AGENT_RUN` | Before `agent.prompt` |
|
|
26
|
+
| `afterAgentRun` | `AFTER_AGENT_RUN` | After `agent.prompt` (success or thrown error) |
|
|
27
|
+
| `beforeCachedAgentReuse` | `BEFORE_CACHED_AGENT_REUSE` | Each cached-agent run cycle (system-prompt rebuild uses this) |
|
|
28
|
+
|
|
29
|
+
There is no `AGENT_ERROR`, `BEFORE_MESSAGE_SEND`, or `AFTER_MESSAGE_RECEIVE` type. Model I/O uses `beforeModelCall` / `afterModelCall` / `messageUpdate`. Tool failures are delivered on `afterToolExecute` with an `error` field.
|
|
30
|
+
|
|
31
|
+
JSON configs may use `postToolExecute` as an alias of `afterToolExecute`, and `preToolExecute` as an alias of `beforeToolExecute`. Kiro names (`PreToolUse`, `PostToolUse`, `PostFileSave`) are rejected.
|
|
32
|
+
|
|
33
|
+
## How agent runs load hooks
|
|
34
|
+
|
|
35
|
+
`initializeHooks(settings)` runs once during bootstrap (`src/agent/hooks/agent-wrapper.js`):
|
|
36
|
+
|
|
37
|
+
1. Clear the global manager.
|
|
38
|
+
2. Register **built-in plugins** (`registerBuiltinHookPlugins`): system-prompt rebuild, then JSON command hooks.
|
|
39
|
+
3. If `.settings.json` has `hooks.active`, register that debug/security preset.
|
|
40
|
+
4. Otherwise, if no `hooks.active`, fall back to env vars (`PITHOSAI_DEBUG_HOOKS`, `PITHOSAI_SECURITY_HOOKS`).
|
|
41
|
+
|
|
42
|
+
`wrapAgentWithHooks` binds a per-run ALS context (`cwd`, `role`, `lang`, `taskId`, `model`, `messages`) around `agent.prompt` and maps agent events onto hook types. `wrapToolWithHooks` fires `beforeToolExecute` with `{ abortOnError: true }` (a thrown handler error returns a JSON `{ ok: false, error }` result and skips the tool) and then `afterToolExecute`.
|
|
43
|
+
|
|
44
|
+
## Add a pithosai core agent hook
|
|
45
|
+
|
|
46
|
+
Do **not** put pithos-owned logic in `~/.pithosai/hooks/` or `<cwd>/.pithosai/hooks/`. Those dirs are for user/project overrides. Core hooks live in `src/agent/hooks/` and register through `registerBuiltinHookPlugins()`.
|
|
47
|
+
|
|
48
|
+
Pick one of two core paths.
|
|
49
|
+
|
|
50
|
+
### Path A — lifecycle plugin (HookManager)
|
|
51
|
+
|
|
52
|
+
Use this when the hook is pithos runtime logic and does not need a JSON `command` (system prompt rebuild is this path).
|
|
53
|
+
|
|
54
|
+
1. Add `src/agent/hooks/<name>.js`. Export a `register…()` that calls `globalHookManager.register(HookType.…, handler)` or `registerAll`.
|
|
55
|
+
2. Handler signature: `async (context) => { …; return context.data; }`. Throw from `beforeToolExecute` to block the tool.
|
|
56
|
+
3. Call that `register…()` from `registerBuiltinHookPlugins()` in `src/agent/hooks/builtin-hook-plugins.js`.
|
|
57
|
+
4. Add a unit test next to the plugin.
|
|
58
|
+
|
|
59
|
+
Do not edit `wrapAgentWithHooks` or `bootstrap.js` to attach a new core hook.
|
|
60
|
+
|
|
61
|
+
### Path B — in-process command adapter (core mapping)
|
|
62
|
+
|
|
63
|
+
Use this when the hook should stay a JSON `command` but run **inside** the agent process (no spawn). The mapping is `CORE_COMMAND_HOOK_ADAPTERS` in `src/agent/hooks/command-hook-plugin.js`. Agent bootstrap passes that map into `registerCommandHookPlugin`. Git AI is **not** in this map.
|
|
64
|
+
|
|
65
|
+
1. Write an adapter:
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
/**
|
|
69
|
+
* @param {string[]} args
|
|
70
|
+
* @param {object} event
|
|
71
|
+
* @returns {Promise<{ ok: boolean; exitCode: number; skipped?: boolean }>}
|
|
72
|
+
*/
|
|
73
|
+
export async function runMyCoreAdapter(args, event) {
|
|
74
|
+
// args = tokens after the command name, e.g. ["rebuild"]
|
|
75
|
+
// event = stdin-shaped payload: hookType, cwd, taskId, name, params, model, messages
|
|
76
|
+
return { ok: true, exitCode: 0, skipped: false };
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Return `exitCode: 2` from `beforeToolExecute` to block the tool. Other non-zero codes are logged and ignored.
|
|
81
|
+
|
|
82
|
+
2. Register the **first argv token** on the core map:
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
export const CORE_COMMAND_HOOK_ADAPTERS = {
|
|
86
|
+
"pithosai-my-adapter": runMyCoreAdapter,
|
|
87
|
+
};
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
3. Add bundled JSON under `src/agent/hooks/configs/<name>.json` whose `command` starts with that token, then list the file on `BUNDLED_COMMAND_HOOK_FILES`:
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
export const BUNDLED_COMMAND_HOOK_FILES = {
|
|
94
|
+
"git-ai.json": bundledGitAi,
|
|
95
|
+
"my-adapter.json": bundledMyAdapter,
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"version": "v1",
|
|
102
|
+
"hooks": [
|
|
103
|
+
{
|
|
104
|
+
"name": "my-core-before-write",
|
|
105
|
+
"enabled": true,
|
|
106
|
+
"matcher": "^write$",
|
|
107
|
+
"timeout": 30,
|
|
108
|
+
"hookType": "beforeToolExecute",
|
|
109
|
+
"command": "pithosai-my-adapter rebuild"
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`resolveCommandHookAction` treats the command as in-process **only** when `argv[0]` is a function on the adapter map. Users can still override or disable that bundled file with a same-name JSON in `~/.pithosai/hooks/` or `<cwd>/.pithosai/hooks/`.
|
|
116
|
+
|
|
117
|
+
4. Add unit tests for the adapter and for the bundled JSON command token.
|
|
118
|
+
|
|
119
|
+
### Path C — external CLI (not core)
|
|
120
|
+
|
|
121
|
+
Use this for integrations that should run out-of-process (Git AI). Put the CLI in `scripts/`, ship it via rspack + `package.json` `bin`, leave `CORE_COMMAND_HOOK_ADAPTERS` empty for that name, and set bundled JSON `command` to `{pithosaiCli:<bin-name>} …`.
|
|
122
|
+
|
|
123
|
+
| Kind | Where it is registered | JSON `command` |
|
|
124
|
+
|------|------------------------|----------------|
|
|
125
|
+
| Core lifecycle | `registerBuiltinHookPlugins()` → `HookManager` | none |
|
|
126
|
+
| Core command adapter | `CORE_COMMAND_HOOK_ADAPTERS` + `BUNDLED_COMMAND_HOOK_FILES` | `pithosai-my-adapter …` |
|
|
127
|
+
| External CLI | `scripts/` + rspack `bin` | `{pithosaiCli:pithosai-git-ai-adapter} …` |
|
|
128
|
+
| User/project override | `~/.pithosai/hooks` or `.pithosai/hooks` | any shell or `{pithosaiCli:…}` |
|
|
129
|
+
|
|
130
|
+
## JSON command hooks
|
|
131
|
+
|
|
132
|
+
Put `*.json` files in:
|
|
133
|
+
|
|
134
|
+
- `~/.pithosai/hooks/` (user)
|
|
135
|
+
- `<cwd>/.pithosai/hooks/` (project)
|
|
136
|
+
|
|
137
|
+
**Precedence by filename:** bundled defaults < user dir < project dir. A project `git-ai.json` replaces the bundled file of the same name. Set `"enabled": false` on every hook in an override file to disable that integration.
|
|
138
|
+
|
|
139
|
+
Invalid JSON files are logged and skipped. Disabled entries and unknown `hookType` values are skipped.
|
|
140
|
+
|
|
141
|
+
### Schema (`version: "v1"`)
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"version": "v1",
|
|
146
|
+
"hooks": [
|
|
147
|
+
{
|
|
148
|
+
"name": "git-ai-before-tool",
|
|
149
|
+
"description": "Optional human-readable description.",
|
|
150
|
+
"enabled": true,
|
|
151
|
+
"matcher": "^(write|edit|read)$",
|
|
152
|
+
"timeout": 30,
|
|
153
|
+
"hookType": "beforeToolExecute",
|
|
154
|
+
"command": "{pithosaiCli:pithosai-git-ai-adapter} checkpoint pre"
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
| Field | Meaning |
|
|
161
|
+
|-------|---------|
|
|
162
|
+
| `name` | Label used in logs and block errors. Defaults to `unnamed-hook`. |
|
|
163
|
+
| `enabled` | `false` skips the entry. Default is enabled. |
|
|
164
|
+
| `matcher` | Optional regex tested against the **tool name**. Empty/`null` matches every tool. If `matcher` is set and the event has no tool name, the hook is skipped. |
|
|
165
|
+
| `timeout` | Seconds. Default `60`. `0` means no timeout. |
|
|
166
|
+
| `hookType` | CamelCase pithos HookType (see table above). |
|
|
167
|
+
| `command` | Shell command, `{pithosaiCli:…}` placeholder, or a registered in-process adapter token. Legacy `action.command` is accepted. |
|
|
168
|
+
|
|
169
|
+
### Stdin event
|
|
170
|
+
|
|
171
|
+
The process receives pithos tool-hook JSON on stdin (not Kiro’s payload):
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"hookType": "beforeToolExecute",
|
|
176
|
+
"cwd": "/repo",
|
|
177
|
+
"taskId": "task-1",
|
|
178
|
+
"name": "write",
|
|
179
|
+
"params": { "path": "src/a.js" },
|
|
180
|
+
"model": "deepseek-v4-flash",
|
|
181
|
+
"messages": [],
|
|
182
|
+
"timestamp": 1710000000000
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`afterToolExecute` may also include `result` or `error` (error is a string). External CLIs must read these fields from stdin; they do not share the agent’s in-process ALS.
|
|
187
|
+
|
|
188
|
+
### Blocking and failures
|
|
189
|
+
|
|
190
|
+
- **`beforeToolExecute` + exit code `2`** blocks the tool (`Hook "<name>" blocked tool "<tool>"`).
|
|
191
|
+
- Any other non-zero exit is logged and **does not** block.
|
|
192
|
+
- Spawn/timeout failures are logged and do not block unless the process exits `2` on `beforeToolExecute`.
|
|
193
|
+
- In-process `beforeToolExecute` handlers that **throw** also block the tool (`abortOnError: true`).
|
|
194
|
+
|
|
195
|
+
### Core in-process adapters vs shell
|
|
196
|
+
|
|
197
|
+
The command-hook runner keeps a generic **adapter map** for pithos core hooks (`CORE_COMMAND_HOOK_ADAPTERS`). A command runs in-process only when its first argv token is a key in that map. The map starts empty: Git AI is not registered there.
|
|
198
|
+
|
|
199
|
+
Anything else is spawned with `shell: true`.
|
|
200
|
+
|
|
201
|
+
`{pithosaiCli:name}` expands to the current Node binary plus the shipped CLI path (`scripts/<name>.js` in a source checkout, `dist/<name>.mjs` after publish). Path-like names (`../…`) are rejected.
|
|
202
|
+
|
|
203
|
+
To add a pithos-owned in-process adapter, see [Add a pithosai core agent hook](#add-a-pithosai-core-agent-hook).
|
|
204
|
+
|
|
205
|
+
### Git AI (external CLI)
|
|
206
|
+
|
|
207
|
+
Git AI is a bundled JSON config plus a shipped CLI, not a pithos core adapter.
|
|
208
|
+
|
|
209
|
+
- Source: `scripts/pithosai-git-ai-adapter.js`
|
|
210
|
+
- Release bin: `pithosai-git-ai-adapter` → `dist/pithosai-git-ai-adapter.mjs`
|
|
211
|
+
- Bundled config: `src/agent/hooks/configs/git-ai.json`
|
|
212
|
+
- Example override: `examples/hooks/git-ai.json`
|
|
213
|
+
|
|
214
|
+
Requires [`git-ai`](https://usegitai.com/docs/agents/add-your-agent) on `PATH`. Which tools are checkpointed is controlled only by the JSON `matcher` (bundled default: `write` / `edit` / `read`). The adapter still needs a file path on tool params. Set `PITHOSAI_GIT_AI=0` (or `false` / `no` / `off`) to skip checkpoints; JSON hooks still load. The CLI always exits `0` so Git AI never blocks a tool.
|
|
215
|
+
|
|
216
|
+
Usage: `pithosai-git-ai-adapter checkpoint pre|post` with hook JSON on stdin.
|
|
217
|
+
|
|
218
|
+
## Built-in plugins
|
|
219
|
+
|
|
220
|
+
Registered by `registerBuiltinHookPlugins()` (do not wire new integrations inside `wrapAgentWithHooks`):
|
|
221
|
+
|
|
222
|
+
- **System prompt rebuild** — `beforeCachedAgentReuse` rebuilds the system prompt from DB role prompts, `.pithosai/rules/*.md`, role `memory.md`, and skills.
|
|
223
|
+
- **JSON command hooks** — loads bundled + user + project JSON, and runs `CORE_COMMAND_HOOK_ADAPTERS` in-process when the command token matches.
|
|
224
|
+
|
|
225
|
+
NPC platform shell policy is a separate in-process plugin (`createNpcSecurityRulesHooks` on `beforeToolExecute`), registered from bootstrap when security rules apply — not via JSON command hooks.
|
|
226
|
+
|
|
227
|
+
## Agent run context
|
|
228
|
+
|
|
229
|
+
Per-run ALS (`src/agent/hooks/agent-run-context.js`) holds `cwd`, `role`, `lang`, `taskId`, `model`, and `messages`. Tool payloads get `taskId` / `model` / `messages` copied on so in-process plugins can read them without ALS. Child processes only see the stdin JSON.
|
|
230
|
+
|
|
231
|
+
## CLI (`pithosai hooks`)
|
|
232
|
+
|
|
233
|
+
Alias: `pithosai hook`.
|
|
234
|
+
|
|
235
|
+
`hooks list` / `status` load **built-in plugins** (system prompt rebuild) and **JSON command hooks** via `registerBuiltinHookPlugins()` and print to stdout — the same plugins an agent run gets from bootstrap. They do **not** read deprecated `.settings.json` `hooks.active`.
|
|
236
|
+
|
|
237
|
+
`hooks debug` / `security` / `clear` still persist a debug/security preset in `<project>/.settings.json` (`hooks.active`). The next agent run may load that preset **after** built-in plugins (legacy path).
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
pithosai hooks list
|
|
241
|
+
pithosai hooks list --verbose
|
|
242
|
+
pithosai hooks status
|
|
243
|
+
|
|
244
|
+
pithosai hooks debug
|
|
245
|
+
pithosai hooks debug --data
|
|
246
|
+
pithosai hooks debug --performance
|
|
247
|
+
pithosai hooks debug --stats
|
|
248
|
+
pithosai hooks debug --all
|
|
249
|
+
|
|
250
|
+
pithosai hooks security --block "exec,bash,write"
|
|
251
|
+
pithosai hooks security --max-prompt 5000
|
|
252
|
+
|
|
253
|
+
pithosai hooks clear
|
|
254
|
+
|
|
255
|
+
pithosai hooks test
|
|
256
|
+
pithosai hooks test beforeAgentRun
|
|
257
|
+
|
|
258
|
+
pithosai hooks register beforeAgentRun ./examples/hooks/custom-logger.js
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`hooks register` requires a default-export function and a camelCase `hookType`. It registers in the **current process** and appends `{ type, file, timestamp }` to `hooks.customHandlers`. Agent bootstrap does **not** re-import those files. For persistent custom behavior, use JSON command hooks.
|
|
262
|
+
|
|
263
|
+
## Environment variables
|
|
264
|
+
|
|
265
|
+
Used when `.settings.json` has **no** `hooks.active`:
|
|
266
|
+
|
|
267
|
+
| Variable | Role |
|
|
268
|
+
|----------|------|
|
|
269
|
+
| `PITHOSAI_DEBUG_HOOKS` | `1` / `true` — debug + performance + stats handlers |
|
|
270
|
+
| `PITHOSAI_DEBUG_HOOKS_DATA` | `1` — log full hook payloads |
|
|
271
|
+
| `PITHOSAI_SECURITY_HOOKS` | `1` / `true` — block listed tools; warn on long prompts / sensitive-looking params |
|
|
272
|
+
| `PITHOSAI_BLOCKED_TOOLS` | Comma-separated tool names (with `PITHOSAI_SECURITY_HOOKS`) |
|
|
273
|
+
| `PITHOSAI_MAX_PROMPT_LENGTH` | Max system-prompt length warning (default `10000`) |
|
|
274
|
+
| `PITHOSAI_GIT_AI` | Git AI CLI checkpoints (on by default). Disable: `0` / `false` / `no` / `off` |
|
|
275
|
+
|
|
276
|
+
## Project settings (`.settings.json`)
|
|
277
|
+
|
|
278
|
+
| Field | Role |
|
|
279
|
+
|-------|------|
|
|
280
|
+
| `hooks.active` | Preset from `pithosai hooks debug` / `security` (`type`: `debug`, `debug-basic`, or `security`, plus `options`) |
|
|
281
|
+
| `hooks.customHandlers` | Metadata from `pithosai hooks register` (not auto-loaded on agent runs) |
|
|
282
|
+
|
|
283
|
+
JSON command hooks are **not** stored here; they live under `~/.pithosai/hooks/` and `<cwd>/.pithosai/hooks/`.
|
|
284
|
+
|
|
285
|
+
## In-process handler shape
|
|
286
|
+
|
|
287
|
+
Handlers receive a **context object**, not the payload as the first argument:
|
|
288
|
+
|
|
289
|
+
```javascript
|
|
290
|
+
/** @param {{ hookType: string; data: object; timestamp: number }} context */
|
|
291
|
+
export default async function customHook(context) {
|
|
292
|
+
const { hookType, data } = context;
|
|
293
|
+
// mutate data when needed
|
|
294
|
+
return data;
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
- Return `data` (optionally modified). Returning `undefined` leaves the previous payload unchanged.
|
|
299
|
+
- Throw from `beforeToolExecute` to block the tool.
|
|
300
|
+
- Keep handlers fast. `messageUpdate` must not await heavy work.
|
|
301
|
+
- Prefer `logErrorToFile` / `logInfoToFile` / `logDebugToFile` over console for TUI runs.
|
|
302
|
+
|
|
303
|
+
### Payload fields (in `context.data`)
|
|
304
|
+
|
|
305
|
+
**`beforeAgentRun` / `afterAgentRun`**
|
|
306
|
+
|
|
307
|
+
`request`, `images`, `cwd`, `role`, `lang`, `timestamp`. After: `result` on success, or `error` on failure.
|
|
308
|
+
|
|
309
|
+
**`beforeToolExecute` / `afterToolExecute`**
|
|
310
|
+
|
|
311
|
+
`name`, `description`, `params`, `toolCallId`, `cwd`, `role`, `lang`, `timestamp`, plus `taskId`, `model`, `messages` when a run context is bound. After: `result` or `error`.
|
|
312
|
+
|
|
313
|
+
**`beforeModelCall` / `afterModelCall` / `messageUpdate`**
|
|
314
|
+
|
|
315
|
+
`messages`, `model`, `cwd`, `role`, `lang`, `timestamp`. After model: `message`. Streaming: `message`, `assistantMessageEvent`.
|
|
316
|
+
|
|
317
|
+
**`beforeToolCall` / `afterToolCall`**
|
|
318
|
+
|
|
319
|
+
`toolCall`, `messages`, `cwd`, `role`, `lang`, `timestamp`. After: `result`.
|
|
320
|
+
|
|
321
|
+
## Troubleshooting
|
|
322
|
+
|
|
323
|
+
- JSON hook not running: confirm the file is `*.json`, `hookType` is camelCase, `enabled` is not `false`, and `matcher` matches the tool name.
|
|
324
|
+
- Tool not blocked: only `beforeToolExecute` exit `2` (JSON) or a thrown in-process handler blocks execution.
|
|
325
|
+
- Git AI skipped: install `git-ai` on `PATH`, leave `PITHOSAI_GIT_AI` unset or on, and keep `git-ai.json` enabled.
|
|
326
|
+
- `hooks list` / `status` show zero handlers: confirm `registerBuiltinHookPlugins` can load (cwd has access; command-hook JSON under `~/.pithosai/hooks` or `<cwd>/.pithosai/hooks`). Builtins always register system-prompt-rebuild when the CLI loads successfully.
|
|
327
|
+
- `hooks register` / `test` “invalid type”: pass `beforeToolExecute`, not `BEFORE_TOOL_EXECUTE`.
|
|
328
|
+
|
|
329
|
+
## Examples
|
|
330
|
+
|
|
331
|
+
- `examples/hooks/git-ai.json` — Git AI JSON command hooks (same shape as the bundled default)
|
|
332
|
+
- `examples/hooks/custom-logger.js` — sketch for `pithosai hooks register` (must default-export a function that accepts hook **context**)
|
|
333
|
+
- `src/agent/hooks/configs/git-ai.json` — bundled default loaded when no user/project file of the same name exists
|
|
334
|
+
- `src/agent/hooks/command-hook-plugin.js` — `CORE_COMMAND_HOOK_ADAPTERS` (pithos core in-process command map) and `BUNDLED_COMMAND_HOOK_FILES`
|
|
335
|
+
- `src/agent/hooks/builtin-hook-plugins.js` — register core lifecycle plugins here
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pithosai/pithosai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.33",
|
|
4
4
|
"description": "Pithos AI CLI — multi-role coding and product agents (bundled distribution).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -22,10 +22,17 @@
|
|
|
22
22
|
"email-monitor": "./dist/heartbeat.mjs",
|
|
23
23
|
"wrsai": "./dist/wrsai",
|
|
24
24
|
"render-markdown": "./dist/render-markdown.mjs",
|
|
25
|
-
"init": "./dist/init.mjs"
|
|
25
|
+
"init": "./dist/init.mjs",
|
|
26
|
+
"pithosai-git-ai-adapter": "./dist/pithosai-git-ai-adapter.mjs"
|
|
26
27
|
},
|
|
27
28
|
"files": [
|
|
28
29
|
"LICENSE",
|
|
30
|
+
"CHANGELOG.md",
|
|
31
|
+
"docs/hooks-usage.md",
|
|
32
|
+
"docs/KROUTER.md",
|
|
33
|
+
"src/models/README.md",
|
|
34
|
+
"src/models/krouter.README.md",
|
|
35
|
+
"src/agent-orch/READEM.md",
|
|
29
36
|
"dist",
|
|
30
37
|
"!dist/npc-platform-client",
|
|
31
38
|
"shims",
|
|
@@ -38,29 +45,37 @@
|
|
|
38
45
|
},
|
|
39
46
|
"dependencies": {
|
|
40
47
|
"@clack/prompts": "^1.0.1",
|
|
41
|
-
"@
|
|
48
|
+
"@earendil-works/pi-agent-core": "0.85.1",
|
|
49
|
+
"@earendil-works/pi-ai": "0.85.1",
|
|
50
|
+
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
51
|
+
"@earendil-works/pi-tui": "0.85.1",
|
|
42
52
|
"@marianmeres/actor": "^1.6.1",
|
|
43
|
-
"@mariozechner/pi-agent-core": "^0.70.6",
|
|
44
|
-
"@mariozechner/pi-ai": "^0.70.6",
|
|
45
|
-
"@mariozechner/pi-coding-agent": "^0.70.6",
|
|
46
|
-
"@mariozechner/pi-tui": "^0.70.6",
|
|
47
53
|
"@sinclair/typebox": "^0.34.49",
|
|
54
|
+
"adm-zip": "^0.5.18",
|
|
48
55
|
"better-sqlite3": "^12.6.2",
|
|
49
56
|
"chalk": "^5.6.2",
|
|
50
57
|
"chokidar": "^5.0.0",
|
|
58
|
+
"cli-table3": "^0.6.5",
|
|
51
59
|
"commander": "^14.0.3",
|
|
60
|
+
"cron-parser": "^4.9.0",
|
|
52
61
|
"date-fns": "^4.1.0",
|
|
53
62
|
"handlebars": "^4.7.8",
|
|
54
63
|
"i18next": "^25.8.4",
|
|
55
64
|
"imap": "^0.8.19",
|
|
65
|
+
"isomorphic-git": "^1.38.6",
|
|
66
|
+
"json5": "^2.2.3",
|
|
56
67
|
"log4js": "^6.9.1",
|
|
68
|
+
"lookpath": "^1.2.3",
|
|
57
69
|
"mailparser": "^3.7.1",
|
|
58
70
|
"marked": "^17.0.2",
|
|
71
|
+
"node-cron": "^3.0.3",
|
|
59
72
|
"nodemailer": "^6.9.16",
|
|
73
|
+
"ollama": "^0.6.3",
|
|
60
74
|
"pdf-parse": "^1.1.1",
|
|
61
75
|
"playwright": "^1.58.2",
|
|
62
76
|
"proper-lockfile": "^4.1.2",
|
|
63
77
|
"readdirp": "^5.0.0",
|
|
78
|
+
"tinyld": "^1.3.4",
|
|
64
79
|
"uuid": "^14.0.0",
|
|
65
80
|
"ws": "^8.20.0"
|
|
66
81
|
},
|
|
@@ -73,8 +88,11 @@
|
|
|
73
88
|
"vitest": "^4.0.18",
|
|
74
89
|
"webpack-node-externals": "^3.0.0"
|
|
75
90
|
},
|
|
91
|
+
"optionalDependencies": {
|
|
92
|
+
"@journeyapps/sqlcipher": "5.3.1"
|
|
93
|
+
},
|
|
76
94
|
"engines": {
|
|
77
|
-
"node": ">=
|
|
95
|
+
"node": ">=22.19.0"
|
|
78
96
|
},
|
|
79
97
|
"publishConfig": {
|
|
80
98
|
"access": "public"
|