@vpxa/aikit 0.1.358 → 0.1.359
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/package.json +1 -1
- package/packages/cli/dist/index.js +19 -19
- package/packages/cli/dist/init-Cvys5TD4.js +7 -0
- package/packages/cli/dist/{templates-Bu7dZtk_.js → templates-d2UQHjpi.js} +13 -12
- package/packages/server/dist/bin.js +1 -1
- package/packages/server/dist/index.js +1 -1
- package/packages/server/dist/prelude-CfXBHLpw.js +1 -0
- package/packages/server/dist/prelude-D33q42vQ.js +2 -0
- package/packages/server/dist/sampling-Cs9MoNYX.js +2 -0
- package/packages/server/dist/sampling-DFogBKrL.js +1 -0
- package/packages/server/dist/{server-D5eu57oU.js → server-BZVPmRGV.js} +160 -160
- package/packages/server/dist/{server-D7gZ4K_H.js → server-CICqmfQ_.js} +160 -160
- package/packages/server/dist/{server-http-AarbD3gm.js → server-http-CPU9_eVT.js} +1 -1
- package/packages/server/dist/{server-http-wnYmWFkC.js → server-http-D3DK1H19.js} +1 -1
- package/packages/server/dist/{server-stdio-BWoMqXU8.js → server-stdio-BRKxFWyO.js} +1 -1
- package/packages/server/dist/{server-stdio-CBGO6XiO.js → server-stdio-l8I0RW4l.js} +1 -1
- package/scaffold/dist/adapters/hooks.mjs +1 -1
- package/scaffold/dist/definitions/exec-hooks.mjs +1 -1
- package/scaffold/dist/definitions/protocols.mjs +7 -6
- package/scaffold/dist/definitions/skills/aikit.mjs +4 -4
- package/scaffold/general/hooks/scripts/_runtime.mjs +87 -15
- package/scaffold/general/hooks/scripts/freshness-signal.mjs +196 -0
- package/scaffold/general/hooks/scripts/privacy-guard.mjs +19 -6
- package/scaffold/general/hooks/scripts/scope-guard.mjs +160 -0
- package/scaffold/general/hooks/scripts/scout-guard.mjs +18 -14
- package/packages/cli/dist/init-CWLiK7bC.js +0 -7
- package/packages/server/dist/prelude-DetbWo8R.js +0 -1
- package/packages/server/dist/prelude-hfAEi93R.js +0 -2
- package/packages/server/dist/sampling-CWjnUevo.js +0 -2
- package/packages/server/dist/sampling-CnE3owSt.js +0 -1
- package/scaffold/general/hooks/scripts/post-edit-check.mjs +0 -36
- package/scaffold/general/hooks/scripts/pre-compact-save.mjs +0 -13
- package/scaffold/general/hooks/scripts/session-init.mjs +0 -85
- package/scaffold/general/hooks/scripts/session-learn.mjs +0 -53
- package/scaffold/general/hooks/scripts/session-observer.mjs +0 -77
- package/scaffold/general/hooks/scripts/subagent-context.mjs +0 -59
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,n as t,r as n,t as r}from"./server-utils-De-aZNQa.js";import{n as i,t as a}from"./startup-maintenance-DX1yTfBa.js";import{createLogger as o,serializeError as s,setDetailedErrorLoggingEnabled as c}from"../../core/dist/index.js";import{randomUUID as l}from"node:crypto";const u=`__pending__:`;function d(e){return e.startsWith(u)}function f(e){let t=e.headers[`mcp-session-id`];return Array.isArray(t)?t[0]:t}function p(e,t,n,r){e.status(t).json({jsonrpc:`2.0`,error:{code:n,message:r},id:null})}var m=class{options;runtimes=new Map;maxSessions;sessionTimeoutMs;now;constructor(e){this.options=e,this.maxSessions=e.maxSessions??8,this.sessionTimeoutMs=(e.sessionTimeoutMinutes??30)*60*1e3,this.now=e.now??(()=>Date.now())}hasSession(e){return this.runtimes.has(e)}getSessionCount(){return this.runtimes.size}async handleRequest(e,t,n=e.body){let r=f(e),i=r?this.runtimes.get(r):void 0;if(r&&!i){p(t,404,-32001,`Session not found`);return}if(!i){if(e.method!==`POST`){p(t,400,-32e3,`Session required`);return}if(i=await this.createRuntime(t),!i)return}await this.withRuntimeLock(i,async()=>{await i.transport.handleRequest(e,t,n),i.lastAccessAt=this.now();let r=i.transport.sessionId??i.id;e.method!==`DELETE`&&!d(r)&&this.options.onSessionActivity?.(r),e.method===`DELETE`&&!d(r)&&await this.closeSession(r,{closeTransport:!1})})}async closeExpiredSessions(){let e=[...this.runtimes.values()].filter(e=>this.now()-e.lastAccessAt>=this.sessionTimeoutMs).map(e=>e.id);for(let t of e)await this.closeSession(t);return e.length}async closeSession(e,t={}){let n=this.runtimes.get(e);return n?(this.runtimes.delete(e),t.notifySessionEnd!==!1&&!d(e)&&this.options.onSessionEnd?.(e),t.closeTransport!==!1&&await n.transport.close().catch(()=>void 0),await n.server.close().catch(()=>void 0),!0):!1}async closeAll(){let e=[...this.runtimes.keys()];for(let t of e)await this.closeSession(t)}async createRuntime(e){if(await this.closeExpiredSessions(),this.runtimes.size>=this.maxSessions){p(e,503,-32003,`Session capacity reached`);return}let t=this.now(),n=await this.options.createServer(),r={id:`${u}${l()}`,transport:void 0,createdAt:t,lastAccessAt:t,server:n,requestChain:Promise.resolve()},i=this.options.createTransport({sessionIdGenerator:()=>l(),onsessioninitialized:async e=>{this.runtimes.delete(r.id),r.id=e,this.runtimes.set(e,r),this.options.onSessionStart?.(e)},onsessionclosed:async e=>{e&&await this.closeSession(e,{closeTransport:!1})}});return r.transport=i,i.onclose=()=>{let e=r.transport.sessionId??r.id;this.closeSession(e,{closeTransport:!1})},this.runtimes.set(r.id,r),await n.connect(i),r}async withRuntimeLock(e,t){let n=e.requestChain,r;e.requestChain=new Promise(e=>{r=e}),await n;try{await t()}finally{r()}}};function h(e){let t=e.includes(`T`)?e:`${e.replace(` `,`T`)}Z`;return Date.parse(t)}var g=class{stateStore;options;gcTimer=null;constructor(e,t={}){this.stateStore=e,this.options=t}onSessionStart(e,t){this.stateStore.sessionCreate(e,t)}onSessionActivity(e){this.stateStore.sessionTouch(e)}onSessionEnd(e){this.stateStore.sessionDelete(e),Promise.resolve(this.options.onSessionEndMaintenance?.(e)).catch(()=>{})}startGC(){if(this.gcTimer)return;let e=(this.options.gcIntervalMinutes??5)*60*1e3;this.gcTimer=setInterval(()=>{this.runGC()},e),this.gcTimer.unref()}runGC(){let e=this.getStaleSessionIds();for(let t of e)this.options.onBeforeSessionDelete?.(t),Promise.resolve(this.options.onSessionEndMaintenance?.(t)).catch(()=>{}),this.stateStore.sessionDelete(t);return e.length}stop(){this.gcTimer&&=(clearInterval(this.gcTimer),null)}getActiveSessions(){return this.stateStore.sessionList().length}listSessions(){return this.stateStore.sessionList()}getStaleSessionIds(e=Date.now()){let t=(this.options.staleTimeoutMinutes??30)*60*1e3;return this.stateStore.sessionList().filter(n=>{let r=h(n.lastActivity);return Number.isFinite(r)&&e-r>=t}).map(e=>e.sessionId)}};const _=o(`server`);async function v(o,u){let[{default:d},{loadConfig:f,resolveIndexMode:p},{registerDashboardRoutes:h,resolveDashboardDir:v},{registerSettingsRoutes:y,resolveSettingsDir:b},{createSettingsRouter:x},{authMiddleware:S,getOrCreateToken:C}]=await Promise.all([import(`express`),import(`./config-BXd38nVn.js`),import(`./dashboard-static-Dw7Nsq4f.js`),import(`./settings-static-BpQgaMRs.js`),import(`./routes-B8hTXXR8.js`),import(`./auth-7LFAZQBu.js`).then(e=>e.t)]),w=f();c(w.logging?.errorDetails===!0),w.configError&&_.warn(`Config load failure`,{error:w.configError}),_.info(`Config loaded`,{sourceCount:w.sources.length,storePath:w.store.path});let T=d();T.use(d.json({limit:`1mb`}));let E=Number(u),D=`http://localhost:${E}`,O=process.env.AIKIT_CORS_ORIGIN??D,k=process.env.AIKIT_ALLOW_ANY_ORIGIN===`true`,A=n(`AIKIT_HTTP_MAX_SESSIONS`,8),j=n(`AIKIT_HTTP_SESSION_TIMEOUT_MINUTES`,30),M=n(`AIKIT_HTTP_SESSION_GC_INTERVAL_MINUTES`,5),N=r({limit:100,windowMs:6e4}),P=!1;T.use((t,n,r)=>{let i=Array.isArray(t.headers.origin)?t.headers.origin[0]:t.headers.origin,a=e({requestOrigin:i,configuredOrigin:O,allowAnyOrigin:k,fallbackOrigin:D});if(a.warn&&!P&&(P=!0,_.warn(`Rejected non-local CORS origin while AIKIT_CORS_ORIGIN=*`,{origin:i,allowAnyOriginEnv:`AIKIT_ALLOW_ANY_ORIGIN=true`})),i&&!a.allowOrigin){n.status(403).json({error:`Origin not allowed`});return}if(a.allowOrigin&&n.setHeader(`Access-Control-Allow-Origin`,a.allowOrigin),n.setHeader(`Access-Control-Allow-Methods`,`GET, POST, PUT, PATCH, DELETE, OPTIONS`),n.setHeader(`Access-Control-Allow-Headers`,`Content-Type, Authorization, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID`),n.setHeader(`Access-Control-Expose-Headers`,`Mcp-Session-Id`),t.method===`OPTIONS`){n.status(204).end();return}r()});let F=C();T.use(S(F)),T.use(`/mcp`,(e,n,r)=>{let i=t(e)??e.ip??e.socket.remoteAddress??`anonymous`;if(N.allow(i)){r();return}let a=Math.max(1,Math.ceil(N.getRetryAfterMs(i)/1e3));n.setHeader(`Retry-After`,String(a)),n.status(429).json({jsonrpc:`2.0`,error:{code:-32003,message:`Rate limit exceeded`},id:null})});let I;T.use(`/mcp`,(e,t,n)=>{if(!I){let t=e.headers[`x-workspace-root`];typeof t==`string`&&t.length>0&&(I=t,_.debug(`Captured workspace root from proxy header`,{wsRoot:t}))}n()}),h(T,v(),_);let L=new Date().toISOString();T.use(`/settings/api`,x({log:_,mcpInfo:()=>({transport:`http`,port:E,pid:process.pid,startedAt:L})})),y(T,b(),_),T.get(`/health`,(e,t)=>{t.json({status:`ok`})});let R=!1,z=null,B=null,V=null,H=null,U=null,W=null,G=null,K=null,q=Promise.resolve(),J=async(e,n)=>{if(!R||!V||!H){n.status(503).json({jsonrpc:`2.0`,error:{code:-32603,message:`Server initializing — please retry in a few seconds`},id:null});return}let r=q,i;q=new Promise(e=>{i=e});let a={POST:6e4,GET:1e4,DELETE:1e4}[e.method]??3e4;if(!await Promise.race([r.then(()=>!0),new Promise(t=>{let n=setTimeout(()=>{_.warn(`mcpLock timed out waiting for previous request, resetting lock chain`,{method:e.method,timeoutMs:a}),t(!1)},a);n.unref&&n.unref()})])&&(q=Promise.resolve(),i=()=>{},W)){let e=W;W=null,G=null,e.close().catch(()=>{})}try{let r=t(e);if(!W){if(r){n.status(404).json({jsonrpc:`2.0`,error:{code:-32001,message:`Session not found`},id:null});return}let e=new H({sessionIdGenerator:()=>l(),onsessioninitialized:async e=>{G=e,B?.onSessionStart(e,{transport:`http`})},onsessionclosed:async e=>{e&&B?.onSessionEnd(e),G=null}});e.onclose=()=>{W===e&&(W=null),G===e.sessionId&&(G=null)},W=e,await V.connect(e)}let i=W;await i.handleRequest(e,n,e.body),e.method!==`DELETE`&&(!r&&i.sessionId?(G=i.sessionId,B?.onSessionStart(i.sessionId,{transport:`http`}),B?.onSessionActivity(i.sessionId)):r&&B?.onSessionActivity(r))}catch(e){if(_.error(`MCP handler error`,s(e)),!n.headersSent){let t=e instanceof Error?e.message:String(e),r=t.includes(`Not Acceptable`);n.status(r?406:500).json({jsonrpc:`2.0`,error:{code:r?-32e3:-32603,message:r?t:`Internal server error`},id:null})}}finally{i()}},Y=async(e,n)=>{let r=t(e);if(U&&(!W||r!==G)){await U.handleRequest(e,n,e.body);return}await J(e,n)};T.post(`/mcp`,Y),T.get(`/mcp`,Y),T.delete(`/mcp`,Y);let X=T.listen(E,`127.0.0.1`,()=>{_.info(`MCP server listening`,{url:`http://127.0.0.1:${E}/mcp`,port:E}),setTimeout(async()=>{try{typeof I==`string`&&I.length>0&&(w.sources[0]={path:I,excludePatterns:w.sources[0]?.excludePatterns??[]},w.allRoots=[I],_.debug(`Workspace root applied from proxy header`,{wsRoot:I}));let[{createLazyServer:e,createMcpServer:t,ALL_TOOL_NAMES:n},{StreamableHTTPServerTransport:r},{checkForUpdates:c,autoUpgradeScaffold:l}]=await Promise.all([import(`./server-D5eu57oU.js`),import(`@modelcontextprotocol/sdk/server/streamableHttp.js`),import(`./version-check-B6bui-YI.js`)]);c(),l(),setInterval(c,1440*60*1e3).unref();let u=!1,d=p(w),f=e(w,d);K=async()=>{f.aikit&&await Promise.all([f.aikit.embedder.shutdown?.().catch(()=>{})??Promise.resolve(),f.aikit.graphStore.close().catch(()=>{}),f.aikit.closeStateStore?.().catch(()=>{})??Promise.resolve(),f.aikit.store.close().catch(()=>{})])},V=f.server,H=r,R=!0,_.debug(`MCP server configured (lazy — AI Kit initializing in background)`,{toolCount:n.length,resourceCount:2}),f.startInit(),f.ready.then(async()=>{try{if(!f.aikit)throw Error(`AI Kit components are not available after initialization`);B=new g(f.aikit.stateStore,{staleTimeoutMinutes:j,gcIntervalMinutes:M,onBeforeSessionDelete:e=>{if(G===e&&W){let e=W;W=null,G=null,e.close().catch(()=>void 0)}U?.closeSession(e,{notifySessionEnd:!1})},onSessionEndMaintenance:async()=>{if(!f.aikit?.curated||!f.aikit?.stateStore)return;let{pruneLessons:e}=await import(`./evolution-BX_zTSdj.js`).then(e=>e.t),t=await e(f.aikit.curated,f.aikit.stateStore,{dryRun:!1});t.pruned.length>0&&_.info(`Session-end lesson prune`,{pruned:t.pruned.length})}}),U=new m({createServer:()=>{if(!f.aikit)throw Error(`AI Kit components are not available after initialization`);return t(f.aikit,w)},createTransport:e=>new r(e),maxSessions:A,sessionTimeoutMinutes:j,onSessionStart:e=>B?.onSessionStart(e,{transport:`http`}),onSessionActivity:e=>B?.onSessionActivity(e),onSessionEnd:e=>B?.onSessionEnd(e)}),B.startGC(),G&&(B.onSessionStart(G,{transport:`http`}),B.onSessionActivity(G)),_.info(`HTTP session runtime ready`,{maxSessions:A,sessionTimeoutMinutes:j,gcIntervalMinutes:M})}catch(e){_.error(`Failed to start session manager`,s(e)),R=!1,u=!0,V=null,H=null,K=null}}),d===`auto`?f.ready.then(async()=>{try{let e=w.sources.map(e=>e.path).join(`, `);_.info(`Running initial index`,{sourcePaths:e}),await f.runInitialIndex(),_.info(`Initial index complete`)}catch(e){_.error(`Initial index failed; will retry on aikit_reindex`,i(o,e))}}):d===`smart`?u||f.ready.then(async()=>{try{if(!f.aikit)throw Error(`AI Kit components are not available after initialization`);let{SmartIndexScheduler:e}=await import(`../../indexer/dist/index.js`),t=new e(f.aikit.indexer,w,f.aikit.store),n=f.aikit.store;z=t,t.start(),n.onBeforeClose&&n.onBeforeClose(()=>t.stop()),f.setSmartScheduler(t),_.debug(`Smart index scheduler started (HTTP mode)`)}catch(e){_.error(`Failed to start smart index scheduler`,i(o,e))}}):_.info(`Initial full indexing skipped in HTTP mode`,{indexMode:d}),f.ready.catch(e=>{_.error(`AI Kit initialization failed`,i(o,e)),R=!1,u=!0,V=null,H=null,K=null}),a(f.ready,()=>f.aikit?{curated:f.aikit.curated,stateStore:f.aikit.stateStore}:null,o)}catch(e){_.error(`Failed to load server modules`,i(o,e)),R=!1,K=null}},100)}),Z=!1,Q=async e=>{Z||(Z=!0,_.info(`Shutdown signal received`,{signal:e}),z?.stop(),B?.stop(),await import(`../../tools/dist/index.js`).then(({processStopAll:e})=>e()).catch(()=>{}),await U?.closeAll().catch(()=>void 0),G&&B?.onSessionEnd(G),W&&(await W.close().catch(()=>void 0),W=null,G=null),X.close(),V&&await V.close(),await K?.().catch(()=>void 0),process.exit(0))};process.on(`SIGINT`,()=>Q(`SIGINT`)),process.on(`SIGTERM`,()=>Q(`SIGTERM`))}export{v as startHttpMode};
|
|
1
|
+
import{a as e,n as t,r as n,t as r}from"./server-utils-De-aZNQa.js";import{n as i,t as a}from"./startup-maintenance-DX1yTfBa.js";import{createLogger as o,serializeError as s,setDetailedErrorLoggingEnabled as c}from"../../core/dist/index.js";import{randomUUID as l}from"node:crypto";const u=`__pending__:`;function d(e){return e.startsWith(u)}function f(e){let t=e.headers[`mcp-session-id`];return Array.isArray(t)?t[0]:t}function p(e,t,n,r){e.status(t).json({jsonrpc:`2.0`,error:{code:n,message:r},id:null})}var m=class{options;runtimes=new Map;maxSessions;sessionTimeoutMs;now;constructor(e){this.options=e,this.maxSessions=e.maxSessions??8,this.sessionTimeoutMs=(e.sessionTimeoutMinutes??30)*60*1e3,this.now=e.now??(()=>Date.now())}hasSession(e){return this.runtimes.has(e)}getSessionCount(){return this.runtimes.size}async handleRequest(e,t,n=e.body){let r=f(e),i=r?this.runtimes.get(r):void 0;if(r&&!i){p(t,404,-32001,`Session not found`);return}if(!i){if(e.method!==`POST`){p(t,400,-32e3,`Session required`);return}if(i=await this.createRuntime(t),!i)return}await this.withRuntimeLock(i,async()=>{await i.transport.handleRequest(e,t,n),i.lastAccessAt=this.now();let r=i.transport.sessionId??i.id;e.method!==`DELETE`&&!d(r)&&this.options.onSessionActivity?.(r),e.method===`DELETE`&&!d(r)&&await this.closeSession(r,{closeTransport:!1})})}async closeExpiredSessions(){let e=[...this.runtimes.values()].filter(e=>this.now()-e.lastAccessAt>=this.sessionTimeoutMs).map(e=>e.id);for(let t of e)await this.closeSession(t);return e.length}async closeSession(e,t={}){let n=this.runtimes.get(e);return n?(this.runtimes.delete(e),t.notifySessionEnd!==!1&&!d(e)&&this.options.onSessionEnd?.(e),t.closeTransport!==!1&&await n.transport.close().catch(()=>void 0),await n.server.close().catch(()=>void 0),!0):!1}async closeAll(){let e=[...this.runtimes.keys()];for(let t of e)await this.closeSession(t)}async createRuntime(e){if(await this.closeExpiredSessions(),this.runtimes.size>=this.maxSessions){p(e,503,-32003,`Session capacity reached`);return}let t=this.now(),n=await this.options.createServer(),r={id:`${u}${l()}`,transport:void 0,createdAt:t,lastAccessAt:t,server:n,requestChain:Promise.resolve()},i=this.options.createTransport({sessionIdGenerator:()=>l(),onsessioninitialized:async e=>{this.runtimes.delete(r.id),r.id=e,this.runtimes.set(e,r),this.options.onSessionStart?.(e)},onsessionclosed:async e=>{e&&await this.closeSession(e,{closeTransport:!1})}});return r.transport=i,i.onclose=()=>{let e=r.transport.sessionId??r.id;this.closeSession(e,{closeTransport:!1})},this.runtimes.set(r.id,r),await n.connect(i),r}async withRuntimeLock(e,t){let n=e.requestChain,r;e.requestChain=new Promise(e=>{r=e}),await n;try{await t()}finally{r()}}};function h(e){let t=e.includes(`T`)?e:`${e.replace(` `,`T`)}Z`;return Date.parse(t)}var g=class{stateStore;options;gcTimer=null;constructor(e,t={}){this.stateStore=e,this.options=t}onSessionStart(e,t){this.stateStore.sessionCreate(e,t)}onSessionActivity(e){this.stateStore.sessionTouch(e)}onSessionEnd(e){this.stateStore.sessionDelete(e),Promise.resolve(this.options.onSessionEndMaintenance?.(e)).catch(()=>{})}startGC(){if(this.gcTimer)return;let e=(this.options.gcIntervalMinutes??5)*60*1e3;this.gcTimer=setInterval(()=>{this.runGC()},e),this.gcTimer.unref()}runGC(){let e=this.getStaleSessionIds();for(let t of e)this.options.onBeforeSessionDelete?.(t),Promise.resolve(this.options.onSessionEndMaintenance?.(t)).catch(()=>{}),this.stateStore.sessionDelete(t);return e.length}stop(){this.gcTimer&&=(clearInterval(this.gcTimer),null)}getActiveSessions(){return this.stateStore.sessionList().length}listSessions(){return this.stateStore.sessionList()}getStaleSessionIds(e=Date.now()){let t=(this.options.staleTimeoutMinutes??30)*60*1e3;return this.stateStore.sessionList().filter(n=>{let r=h(n.lastActivity);return Number.isFinite(r)&&e-r>=t}).map(e=>e.sessionId)}};const _=o(`server`);async function v(o,u){let[{default:d},{loadConfig:f,resolveIndexMode:p},{registerDashboardRoutes:h,resolveDashboardDir:v},{registerSettingsRoutes:y,resolveSettingsDir:b},{createSettingsRouter:x},{authMiddleware:S,getOrCreateToken:C}]=await Promise.all([import(`express`),import(`./config-BXd38nVn.js`),import(`./dashboard-static-Dw7Nsq4f.js`),import(`./settings-static-BpQgaMRs.js`),import(`./routes-B8hTXXR8.js`),import(`./auth-7LFAZQBu.js`).then(e=>e.t)]),w=f();c(w.logging?.errorDetails===!0),w.configError&&_.warn(`Config load failure`,{error:w.configError}),_.info(`Config loaded`,{sourceCount:w.sources.length,storePath:w.store.path});let T=d();T.use(d.json({limit:`1mb`}));let E=Number(u),D=`http://localhost:${E}`,O=process.env.AIKIT_CORS_ORIGIN??D,k=process.env.AIKIT_ALLOW_ANY_ORIGIN===`true`,A=n(`AIKIT_HTTP_MAX_SESSIONS`,8),j=n(`AIKIT_HTTP_SESSION_TIMEOUT_MINUTES`,30),M=n(`AIKIT_HTTP_SESSION_GC_INTERVAL_MINUTES`,5),N=r({limit:100,windowMs:6e4}),P=!1;T.use((t,n,r)=>{let i=Array.isArray(t.headers.origin)?t.headers.origin[0]:t.headers.origin,a=e({requestOrigin:i,configuredOrigin:O,allowAnyOrigin:k,fallbackOrigin:D});if(a.warn&&!P&&(P=!0,_.warn(`Rejected non-local CORS origin while AIKIT_CORS_ORIGIN=*`,{origin:i,allowAnyOriginEnv:`AIKIT_ALLOW_ANY_ORIGIN=true`})),i&&!a.allowOrigin){n.status(403).json({error:`Origin not allowed`});return}if(a.allowOrigin&&n.setHeader(`Access-Control-Allow-Origin`,a.allowOrigin),n.setHeader(`Access-Control-Allow-Methods`,`GET, POST, PUT, PATCH, DELETE, OPTIONS`),n.setHeader(`Access-Control-Allow-Headers`,`Content-Type, Authorization, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID`),n.setHeader(`Access-Control-Expose-Headers`,`Mcp-Session-Id`),t.method===`OPTIONS`){n.status(204).end();return}r()});let F=C();T.use(S(F)),T.use(`/mcp`,(e,n,r)=>{let i=t(e)??e.ip??e.socket.remoteAddress??`anonymous`;if(N.allow(i)){r();return}let a=Math.max(1,Math.ceil(N.getRetryAfterMs(i)/1e3));n.setHeader(`Retry-After`,String(a)),n.status(429).json({jsonrpc:`2.0`,error:{code:-32003,message:`Rate limit exceeded`},id:null})});let I;T.use(`/mcp`,(e,t,n)=>{if(!I){let t=e.headers[`x-workspace-root`];typeof t==`string`&&t.length>0&&(I=t,_.debug(`Captured workspace root from proxy header`,{wsRoot:t}))}n()}),h(T,v(),_);let L=new Date().toISOString();T.use(`/settings/api`,x({log:_,mcpInfo:()=>({transport:`http`,port:E,pid:process.pid,startedAt:L})})),y(T,b(),_),T.get(`/health`,(e,t)=>{t.json({status:`ok`})});let R=!1,z=null,B=null,V=null,H=null,U=null,W=null,G=null,K=null,q=Promise.resolve(),J=async(e,n)=>{if(!R||!V||!H){n.status(503).json({jsonrpc:`2.0`,error:{code:-32603,message:`Server initializing — please retry in a few seconds`},id:null});return}let r=q,i;q=new Promise(e=>{i=e});let a={POST:6e4,GET:1e4,DELETE:1e4}[e.method]??3e4;if(!await Promise.race([r.then(()=>!0),new Promise(t=>{let n=setTimeout(()=>{_.warn(`mcpLock timed out waiting for previous request, resetting lock chain`,{method:e.method,timeoutMs:a}),t(!1)},a);n.unref&&n.unref()})])&&(q=Promise.resolve(),i=()=>{},W)){let e=W;W=null,G=null,e.close().catch(()=>{})}try{let r=t(e);if(!W){if(r){n.status(404).json({jsonrpc:`2.0`,error:{code:-32001,message:`Session not found`},id:null});return}let e=new H({sessionIdGenerator:()=>l(),onsessioninitialized:async e=>{G=e,B?.onSessionStart(e,{transport:`http`})},onsessionclosed:async e=>{e&&B?.onSessionEnd(e),G=null}});e.onclose=()=>{W===e&&(W=null),G===e.sessionId&&(G=null)},W=e,await V.connect(e)}let i=W;await i.handleRequest(e,n,e.body),e.method!==`DELETE`&&(!r&&i.sessionId?(G=i.sessionId,B?.onSessionStart(i.sessionId,{transport:`http`}),B?.onSessionActivity(i.sessionId)):r&&B?.onSessionActivity(r))}catch(e){if(_.error(`MCP handler error`,s(e)),!n.headersSent){let t=e instanceof Error?e.message:String(e),r=t.includes(`Not Acceptable`);n.status(r?406:500).json({jsonrpc:`2.0`,error:{code:r?-32e3:-32603,message:r?t:`Internal server error`},id:null})}}finally{i()}},Y=async(e,n)=>{let r=t(e);if(U&&(!W||r!==G)){await U.handleRequest(e,n,e.body);return}await J(e,n)};T.post(`/mcp`,Y),T.get(`/mcp`,Y),T.delete(`/mcp`,Y);let X=T.listen(E,`127.0.0.1`,()=>{_.info(`MCP server listening`,{url:`http://127.0.0.1:${E}/mcp`,port:E}),setTimeout(async()=>{try{typeof I==`string`&&I.length>0&&(w.sources[0]={path:I,excludePatterns:w.sources[0]?.excludePatterns??[]},w.allRoots=[I],_.debug(`Workspace root applied from proxy header`,{wsRoot:I}));let[{createLazyServer:e,createMcpServer:t,ALL_TOOL_NAMES:n},{StreamableHTTPServerTransport:r},{checkForUpdates:c,autoUpgradeScaffold:l}]=await Promise.all([import(`./server-BZVPmRGV.js`),import(`@modelcontextprotocol/sdk/server/streamableHttp.js`),import(`./version-check-B6bui-YI.js`)]);c(),l(),setInterval(c,1440*60*1e3).unref();let u=!1,d=p(w),f=e(w,d);K=async()=>{f.aikit&&await Promise.all([f.aikit.embedder.shutdown?.().catch(()=>{})??Promise.resolve(),f.aikit.graphStore.close().catch(()=>{}),f.aikit.closeStateStore?.().catch(()=>{})??Promise.resolve(),f.aikit.store.close().catch(()=>{})])},V=f.server,H=r,R=!0,_.debug(`MCP server configured (lazy — AI Kit initializing in background)`,{toolCount:n.length,resourceCount:2}),f.startInit(),f.ready.then(async()=>{try{if(!f.aikit)throw Error(`AI Kit components are not available after initialization`);B=new g(f.aikit.stateStore,{staleTimeoutMinutes:j,gcIntervalMinutes:M,onBeforeSessionDelete:e=>{if(G===e&&W){let e=W;W=null,G=null,e.close().catch(()=>void 0)}U?.closeSession(e,{notifySessionEnd:!1})},onSessionEndMaintenance:async()=>{if(!f.aikit?.curated||!f.aikit?.stateStore)return;let{pruneLessons:e}=await import(`./evolution-BX_zTSdj.js`).then(e=>e.t),t=await e(f.aikit.curated,f.aikit.stateStore,{dryRun:!1});t.pruned.length>0&&_.info(`Session-end lesson prune`,{pruned:t.pruned.length})}}),U=new m({createServer:()=>{if(!f.aikit)throw Error(`AI Kit components are not available after initialization`);return t(f.aikit,w)},createTransport:e=>new r(e),maxSessions:A,sessionTimeoutMinutes:j,onSessionStart:e=>B?.onSessionStart(e,{transport:`http`}),onSessionActivity:e=>B?.onSessionActivity(e),onSessionEnd:e=>B?.onSessionEnd(e)}),B.startGC(),G&&(B.onSessionStart(G,{transport:`http`}),B.onSessionActivity(G)),_.info(`HTTP session runtime ready`,{maxSessions:A,sessionTimeoutMinutes:j,gcIntervalMinutes:M})}catch(e){_.error(`Failed to start session manager`,s(e)),R=!1,u=!0,V=null,H=null,K=null}}),d===`auto`?f.ready.then(async()=>{try{let e=w.sources.map(e=>e.path).join(`, `);_.info(`Running initial index`,{sourcePaths:e}),await f.runInitialIndex(),_.info(`Initial index complete`)}catch(e){_.error(`Initial index failed; will retry on aikit_reindex`,i(o,e))}}):d===`smart`?u||f.ready.then(async()=>{try{if(!f.aikit)throw Error(`AI Kit components are not available after initialization`);let{SmartIndexScheduler:e}=await import(`../../indexer/dist/index.js`),t=new e(f.aikit.indexer,w,f.aikit.store),n=f.aikit.store;z=t,t.start(),n.onBeforeClose&&n.onBeforeClose(()=>t.stop()),f.setSmartScheduler(t),_.debug(`Smart index scheduler started (HTTP mode)`)}catch(e){_.error(`Failed to start smart index scheduler`,i(o,e))}}):_.info(`Initial full indexing skipped in HTTP mode`,{indexMode:d}),f.ready.catch(e=>{_.error(`AI Kit initialization failed`,i(o,e)),R=!1,u=!0,V=null,H=null,K=null}),a(f.ready,()=>f.aikit?{curated:f.aikit.curated,stateStore:f.aikit.stateStore}:null,o)}catch(e){_.error(`Failed to load server modules`,i(o,e)),R=!1,K=null}},100)}),Z=!1,Q=async e=>{Z||(Z=!0,_.info(`Shutdown signal received`,{signal:e}),z?.stop(),B?.stop(),await import(`../../tools/dist/index.js`).then(({processStopAll:e})=>e()).catch(()=>{}),await U?.closeAll().catch(()=>void 0),G&&B?.onSessionEnd(G),W&&(await W.close().catch(()=>void 0),W=null,G=null),X.close(),V&&await V.close(),await K?.().catch(()=>void 0),process.exit(0))};process.on(`SIGINT`,()=>Q(`SIGINT`)),process.on(`SIGTERM`,()=>Q(`SIGTERM`))}export{v as startHttpMode};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as e,i as t,o as n,s as r}from"./bin.js";import{n as i,t as a}from"./startup-maintenance-CBJWiJ7p.js";import{createLogger as o,serializeError as s,setDetailedErrorLoggingEnabled as c}from"../../core/dist/index.js";import{randomUUID as l}from"node:crypto";const u=`__pending__:`;function d(e){return e.startsWith(u)}function f(e){let t=e.headers[`mcp-session-id`];return Array.isArray(t)?t[0]:t}function p(e,t,n,r){e.status(t).json({jsonrpc:`2.0`,error:{code:n,message:r},id:null})}var m=class{options;runtimes=new Map;maxSessions;sessionTimeoutMs;now;constructor(e){this.options=e,this.maxSessions=e.maxSessions??8,this.sessionTimeoutMs=(e.sessionTimeoutMinutes??30)*60*1e3,this.now=e.now??(()=>Date.now())}hasSession(e){return this.runtimes.has(e)}getSessionCount(){return this.runtimes.size}async handleRequest(e,t,n=e.body){let r=f(e),i=r?this.runtimes.get(r):void 0;if(r&&!i){p(t,404,-32001,`Session not found`);return}if(!i){if(e.method!==`POST`){p(t,400,-32e3,`Session required`);return}if(i=await this.createRuntime(t),!i)return}await this.withRuntimeLock(i,async()=>{await i.transport.handleRequest(e,t,n),i.lastAccessAt=this.now();let r=i.transport.sessionId??i.id;e.method!==`DELETE`&&!d(r)&&this.options.onSessionActivity?.(r),e.method===`DELETE`&&!d(r)&&await this.closeSession(r,{closeTransport:!1})})}async closeExpiredSessions(){let e=[...this.runtimes.values()].filter(e=>this.now()-e.lastAccessAt>=this.sessionTimeoutMs).map(e=>e.id);for(let t of e)await this.closeSession(t);return e.length}async closeSession(e,t={}){let n=this.runtimes.get(e);return n?(this.runtimes.delete(e),t.notifySessionEnd!==!1&&!d(e)&&this.options.onSessionEnd?.(e),t.closeTransport!==!1&&await n.transport.close().catch(()=>void 0),await n.server.close().catch(()=>void 0),!0):!1}async closeAll(){let e=[...this.runtimes.keys()];for(let t of e)await this.closeSession(t)}async createRuntime(e){if(await this.closeExpiredSessions(),this.runtimes.size>=this.maxSessions){p(e,503,-32003,`Session capacity reached`);return}let t=this.now(),n=await this.options.createServer(),r={id:`${u}${l()}`,transport:void 0,createdAt:t,lastAccessAt:t,server:n,requestChain:Promise.resolve()},i=this.options.createTransport({sessionIdGenerator:()=>l(),onsessioninitialized:async e=>{this.runtimes.delete(r.id),r.id=e,this.runtimes.set(e,r),this.options.onSessionStart?.(e)},onsessionclosed:async e=>{e&&await this.closeSession(e,{closeTransport:!1})}});return r.transport=i,i.onclose=()=>{let e=r.transport.sessionId??r.id;this.closeSession(e,{closeTransport:!1})},this.runtimes.set(r.id,r),await n.connect(i),r}async withRuntimeLock(e,t){let n=e.requestChain,r;e.requestChain=new Promise(e=>{r=e}),await n;try{await t()}finally{r()}}};function h(e){let t=e.includes(`T`)?e:`${e.replace(` `,`T`)}Z`;return Date.parse(t)}var g=class{stateStore;options;gcTimer=null;constructor(e,t={}){this.stateStore=e,this.options=t}onSessionStart(e,t){this.stateStore.sessionCreate(e,t)}onSessionActivity(e){this.stateStore.sessionTouch(e)}onSessionEnd(e){this.stateStore.sessionDelete(e),Promise.resolve(this.options.onSessionEndMaintenance?.(e)).catch(()=>{})}startGC(){if(this.gcTimer)return;let e=(this.options.gcIntervalMinutes??5)*60*1e3;this.gcTimer=setInterval(()=>{this.runGC()},e),this.gcTimer.unref()}runGC(){let e=this.getStaleSessionIds();for(let t of e)this.options.onBeforeSessionDelete?.(t),Promise.resolve(this.options.onSessionEndMaintenance?.(t)).catch(()=>{}),this.stateStore.sessionDelete(t);return e.length}stop(){this.gcTimer&&=(clearInterval(this.gcTimer),null)}getActiveSessions(){return this.stateStore.sessionList().length}listSessions(){return this.stateStore.sessionList()}getStaleSessionIds(e=Date.now()){let t=(this.options.staleTimeoutMinutes??30)*60*1e3;return this.stateStore.sessionList().filter(n=>{let r=h(n.lastActivity);return Number.isFinite(r)&&e-r>=t}).map(e=>e.sessionId)}};const _=o(`server`);async function v(o,u){let[{default:d},{loadConfig:f,resolveIndexMode:p},{registerDashboardRoutes:h,resolveDashboardDir:v},{registerSettingsRoutes:y,resolveSettingsDir:b},{createSettingsRouter:x},{authMiddleware:S,getOrCreateToken:C}]=await Promise.all([import(`express`),import(`./config-4M54ZAT8.js`),import(`./dashboard-static-dPnij4uF.js`),import(`./settings-static-MepJZjer.js`),import(`./routes-9KnBpwfe.js`),import(`./auth-bEP-6uqy.js`)]),w=f();c(w.logging?.errorDetails===!0),w.configError&&_.warn(`Config load failure`,{error:w.configError}),_.info(`Config loaded`,{sourceCount:w.sources.length,storePath:w.store.path});let T=d();T.use(d.json({limit:`1mb`}));let E=Number(u),D=`http://localhost:${E}`,O=process.env.AIKIT_CORS_ORIGIN??D,k=process.env.AIKIT_ALLOW_ANY_ORIGIN===`true`,A=n(`AIKIT_HTTP_MAX_SESSIONS`,8),j=n(`AIKIT_HTTP_SESSION_TIMEOUT_MINUTES`,30),M=n(`AIKIT_HTTP_SESSION_GC_INTERVAL_MINUTES`,5),N=t({limit:100,windowMs:6e4}),P=!1;T.use((e,t,n)=>{let i=Array.isArray(e.headers.origin)?e.headers.origin[0]:e.headers.origin,a=r({requestOrigin:i,configuredOrigin:O,allowAnyOrigin:k,fallbackOrigin:D});if(a.warn&&!P&&(P=!0,_.warn(`Rejected non-local CORS origin while AIKIT_CORS_ORIGIN=*`,{origin:i,allowAnyOriginEnv:`AIKIT_ALLOW_ANY_ORIGIN=true`})),i&&!a.allowOrigin){t.status(403).json({error:`Origin not allowed`});return}if(a.allowOrigin&&t.setHeader(`Access-Control-Allow-Origin`,a.allowOrigin),t.setHeader(`Access-Control-Allow-Methods`,`GET, POST, PUT, PATCH, DELETE, OPTIONS`),t.setHeader(`Access-Control-Allow-Headers`,`Content-Type, Authorization, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID`),t.setHeader(`Access-Control-Expose-Headers`,`Mcp-Session-Id`),e.method===`OPTIONS`){t.status(204).end();return}n()});let F=C();T.use(S(F)),T.use(`/mcp`,(t,n,r)=>{let i=e(t)??t.ip??t.socket.remoteAddress??`anonymous`;if(N.allow(i)){r();return}let a=Math.max(1,Math.ceil(N.getRetryAfterMs(i)/1e3));n.setHeader(`Retry-After`,String(a)),n.status(429).json({jsonrpc:`2.0`,error:{code:-32003,message:`Rate limit exceeded`},id:null})});let I;T.use(`/mcp`,(e,t,n)=>{if(!I){let t=e.headers[`x-workspace-root`];typeof t==`string`&&t.length>0&&(I=t,_.debug(`Captured workspace root from proxy header`,{wsRoot:t}))}n()}),h(T,v(),_);let L=new Date().toISOString();T.use(`/settings/api`,x({log:_,mcpInfo:()=>({transport:`http`,port:E,pid:process.pid,startedAt:L})})),y(T,b(),_),T.get(`/health`,(e,t)=>{t.json({status:`ok`})});let R=!1,z=null,B=null,V=null,H=null,U=null,W=null,G=null,K=null,q=Promise.resolve(),J=async(t,n)=>{if(!R||!V||!H){n.status(503).json({jsonrpc:`2.0`,error:{code:-32603,message:`Server initializing — please retry in a few seconds`},id:null});return}let r=q,i;q=new Promise(e=>{i=e});let a={POST:6e4,GET:1e4,DELETE:1e4}[t.method]??3e4;if(!await Promise.race([r.then(()=>!0),new Promise(e=>{let n=setTimeout(()=>{_.warn(`mcpLock timed out waiting for previous request, resetting lock chain`,{method:t.method,timeoutMs:a}),e(!1)},a);n.unref&&n.unref()})])&&(q=Promise.resolve(),i=()=>{},W)){let e=W;W=null,G=null,e.close().catch(()=>{})}try{let r=e(t);if(!W){if(r){n.status(404).json({jsonrpc:`2.0`,error:{code:-32001,message:`Session not found`},id:null});return}let e=new H({sessionIdGenerator:()=>l(),onsessioninitialized:async e=>{G=e,B?.onSessionStart(e,{transport:`http`})},onsessionclosed:async e=>{e&&B?.onSessionEnd(e),G=null}});e.onclose=()=>{W===e&&(W=null),G===e.sessionId&&(G=null)},W=e,await V.connect(e)}let i=W;await i.handleRequest(t,n,t.body),t.method!==`DELETE`&&(!r&&i.sessionId?(G=i.sessionId,B?.onSessionStart(i.sessionId,{transport:`http`}),B?.onSessionActivity(i.sessionId)):r&&B?.onSessionActivity(r))}catch(e){if(_.error(`MCP handler error`,s(e)),!n.headersSent){let t=e instanceof Error?e.message:String(e),r=t.includes(`Not Acceptable`);n.status(r?406:500).json({jsonrpc:`2.0`,error:{code:r?-32e3:-32603,message:r?t:`Internal server error`},id:null})}}finally{i()}},Y=async(t,n)=>{let r=e(t);if(U&&(!W||r!==G)){await U.handleRequest(t,n,t.body);return}await J(t,n)};T.post(`/mcp`,Y),T.get(`/mcp`,Y),T.delete(`/mcp`,Y);let X=T.listen(E,`127.0.0.1`,()=>{_.info(`MCP server listening`,{url:`http://127.0.0.1:${E}/mcp`,port:E}),setTimeout(async()=>{try{typeof I==`string`&&I.length>0&&(w.sources[0]={path:I,excludePatterns:w.sources[0]?.excludePatterns??[]},w.allRoots=[I],_.debug(`Workspace root applied from proxy header`,{wsRoot:I}));let[{createLazyServer:e,createMcpServer:t,ALL_TOOL_NAMES:n},{StreamableHTTPServerTransport:r},{checkForUpdates:c,autoUpgradeScaffold:l}]=await Promise.all([import(`./server-D7gZ4K_H.js`),import(`@modelcontextprotocol/sdk/server/streamableHttp.js`),import(`./version-check-CZ5hY6R2.js`)]);c(),l(),setInterval(c,1440*60*1e3).unref();let u=!1,d=p(w),f=e(w,d);K=async()=>{f.aikit&&await Promise.all([f.aikit.embedder.shutdown?.().catch(()=>{})??Promise.resolve(),f.aikit.graphStore.close().catch(()=>{}),f.aikit.closeStateStore?.().catch(()=>{})??Promise.resolve(),f.aikit.store.close().catch(()=>{})])},V=f.server,H=r,R=!0,_.debug(`MCP server configured (lazy — AI Kit initializing in background)`,{toolCount:n.length,resourceCount:2}),f.startInit(),f.ready.then(async()=>{try{if(!f.aikit)throw Error(`AI Kit components are not available after initialization`);B=new g(f.aikit.stateStore,{staleTimeoutMinutes:j,gcIntervalMinutes:M,onBeforeSessionDelete:e=>{if(G===e&&W){let e=W;W=null,G=null,e.close().catch(()=>void 0)}U?.closeSession(e,{notifySessionEnd:!1})},onSessionEndMaintenance:async()=>{if(!f.aikit?.curated||!f.aikit?.stateStore)return;let{pruneLessons:e}=await import(`./evolution-DWaEE6XW.js`).then(e=>e.t),t=await e(f.aikit.curated,f.aikit.stateStore,{dryRun:!1});t.pruned.length>0&&_.info(`Session-end lesson prune`,{pruned:t.pruned.length})}}),U=new m({createServer:()=>{if(!f.aikit)throw Error(`AI Kit components are not available after initialization`);return t(f.aikit,w)},createTransport:e=>new r(e),maxSessions:A,sessionTimeoutMinutes:j,onSessionStart:e=>B?.onSessionStart(e,{transport:`http`}),onSessionActivity:e=>B?.onSessionActivity(e),onSessionEnd:e=>B?.onSessionEnd(e)}),B.startGC(),G&&(B.onSessionStart(G,{transport:`http`}),B.onSessionActivity(G)),_.info(`HTTP session runtime ready`,{maxSessions:A,sessionTimeoutMinutes:j,gcIntervalMinutes:M})}catch(e){_.error(`Failed to start session manager`,s(e)),R=!1,u=!0,V=null,H=null,K=null}}),d===`auto`?f.ready.then(async()=>{try{let e=w.sources.map(e=>e.path).join(`, `);_.info(`Running initial index`,{sourcePaths:e}),await f.runInitialIndex(),_.info(`Initial index complete`)}catch(e){_.error(`Initial index failed; will retry on aikit_reindex`,i(o,e))}}):d===`smart`?u||f.ready.then(async()=>{try{if(!f.aikit)throw Error(`AI Kit components are not available after initialization`);let{SmartIndexScheduler:e}=await import(`../../indexer/dist/index.js`),t=new e(f.aikit.indexer,w,f.aikit.store),n=f.aikit.store;z=t,t.start(),n.onBeforeClose&&n.onBeforeClose(()=>t.stop()),f.setSmartScheduler(t),_.debug(`Smart index scheduler started (HTTP mode)`)}catch(e){_.error(`Failed to start smart index scheduler`,i(o,e))}}):_.info(`Initial full indexing skipped in HTTP mode`,{indexMode:d}),f.ready.catch(e=>{_.error(`AI Kit initialization failed`,i(o,e)),R=!1,u=!0,V=null,H=null,K=null}),a(f.ready,()=>f.aikit?{curated:f.aikit.curated,stateStore:f.aikit.stateStore}:null,o)}catch(e){_.error(`Failed to load server modules`,i(o,e)),R=!1,K=null}},100)}),Z=!1,Q=async e=>{Z||(Z=!0,_.info(`Shutdown signal received`,{signal:e}),z?.stop(),B?.stop(),await import(`../../tools/dist/index.js`).then(({processStopAll:e})=>e()).catch(()=>{}),await U?.closeAll().catch(()=>void 0),G&&B?.onSessionEnd(G),W&&(await W.close().catch(()=>void 0),W=null,G=null),X.close(),V&&await V.close(),await K?.().catch(()=>void 0),process.exit(0))};process.on(`SIGINT`,()=>Q(`SIGINT`)),process.on(`SIGTERM`,()=>Q(`SIGTERM`))}export{v as startHttpMode};
|
|
2
|
+
import{a as e,i as t,o as n,s as r}from"./bin.js";import{n as i,t as a}from"./startup-maintenance-CBJWiJ7p.js";import{createLogger as o,serializeError as s,setDetailedErrorLoggingEnabled as c}from"../../core/dist/index.js";import{randomUUID as l}from"node:crypto";const u=`__pending__:`;function d(e){return e.startsWith(u)}function f(e){let t=e.headers[`mcp-session-id`];return Array.isArray(t)?t[0]:t}function p(e,t,n,r){e.status(t).json({jsonrpc:`2.0`,error:{code:n,message:r},id:null})}var m=class{options;runtimes=new Map;maxSessions;sessionTimeoutMs;now;constructor(e){this.options=e,this.maxSessions=e.maxSessions??8,this.sessionTimeoutMs=(e.sessionTimeoutMinutes??30)*60*1e3,this.now=e.now??(()=>Date.now())}hasSession(e){return this.runtimes.has(e)}getSessionCount(){return this.runtimes.size}async handleRequest(e,t,n=e.body){let r=f(e),i=r?this.runtimes.get(r):void 0;if(r&&!i){p(t,404,-32001,`Session not found`);return}if(!i){if(e.method!==`POST`){p(t,400,-32e3,`Session required`);return}if(i=await this.createRuntime(t),!i)return}await this.withRuntimeLock(i,async()=>{await i.transport.handleRequest(e,t,n),i.lastAccessAt=this.now();let r=i.transport.sessionId??i.id;e.method!==`DELETE`&&!d(r)&&this.options.onSessionActivity?.(r),e.method===`DELETE`&&!d(r)&&await this.closeSession(r,{closeTransport:!1})})}async closeExpiredSessions(){let e=[...this.runtimes.values()].filter(e=>this.now()-e.lastAccessAt>=this.sessionTimeoutMs).map(e=>e.id);for(let t of e)await this.closeSession(t);return e.length}async closeSession(e,t={}){let n=this.runtimes.get(e);return n?(this.runtimes.delete(e),t.notifySessionEnd!==!1&&!d(e)&&this.options.onSessionEnd?.(e),t.closeTransport!==!1&&await n.transport.close().catch(()=>void 0),await n.server.close().catch(()=>void 0),!0):!1}async closeAll(){let e=[...this.runtimes.keys()];for(let t of e)await this.closeSession(t)}async createRuntime(e){if(await this.closeExpiredSessions(),this.runtimes.size>=this.maxSessions){p(e,503,-32003,`Session capacity reached`);return}let t=this.now(),n=await this.options.createServer(),r={id:`${u}${l()}`,transport:void 0,createdAt:t,lastAccessAt:t,server:n,requestChain:Promise.resolve()},i=this.options.createTransport({sessionIdGenerator:()=>l(),onsessioninitialized:async e=>{this.runtimes.delete(r.id),r.id=e,this.runtimes.set(e,r),this.options.onSessionStart?.(e)},onsessionclosed:async e=>{e&&await this.closeSession(e,{closeTransport:!1})}});return r.transport=i,i.onclose=()=>{let e=r.transport.sessionId??r.id;this.closeSession(e,{closeTransport:!1})},this.runtimes.set(r.id,r),await n.connect(i),r}async withRuntimeLock(e,t){let n=e.requestChain,r;e.requestChain=new Promise(e=>{r=e}),await n;try{await t()}finally{r()}}};function h(e){let t=e.includes(`T`)?e:`${e.replace(` `,`T`)}Z`;return Date.parse(t)}var g=class{stateStore;options;gcTimer=null;constructor(e,t={}){this.stateStore=e,this.options=t}onSessionStart(e,t){this.stateStore.sessionCreate(e,t)}onSessionActivity(e){this.stateStore.sessionTouch(e)}onSessionEnd(e){this.stateStore.sessionDelete(e),Promise.resolve(this.options.onSessionEndMaintenance?.(e)).catch(()=>{})}startGC(){if(this.gcTimer)return;let e=(this.options.gcIntervalMinutes??5)*60*1e3;this.gcTimer=setInterval(()=>{this.runGC()},e),this.gcTimer.unref()}runGC(){let e=this.getStaleSessionIds();for(let t of e)this.options.onBeforeSessionDelete?.(t),Promise.resolve(this.options.onSessionEndMaintenance?.(t)).catch(()=>{}),this.stateStore.sessionDelete(t);return e.length}stop(){this.gcTimer&&=(clearInterval(this.gcTimer),null)}getActiveSessions(){return this.stateStore.sessionList().length}listSessions(){return this.stateStore.sessionList()}getStaleSessionIds(e=Date.now()){let t=(this.options.staleTimeoutMinutes??30)*60*1e3;return this.stateStore.sessionList().filter(n=>{let r=h(n.lastActivity);return Number.isFinite(r)&&e-r>=t}).map(e=>e.sessionId)}};const _=o(`server`);async function v(o,u){let[{default:d},{loadConfig:f,resolveIndexMode:p},{registerDashboardRoutes:h,resolveDashboardDir:v},{registerSettingsRoutes:y,resolveSettingsDir:b},{createSettingsRouter:x},{authMiddleware:S,getOrCreateToken:C}]=await Promise.all([import(`express`),import(`./config-4M54ZAT8.js`),import(`./dashboard-static-dPnij4uF.js`),import(`./settings-static-MepJZjer.js`),import(`./routes-9KnBpwfe.js`),import(`./auth-bEP-6uqy.js`)]),w=f();c(w.logging?.errorDetails===!0),w.configError&&_.warn(`Config load failure`,{error:w.configError}),_.info(`Config loaded`,{sourceCount:w.sources.length,storePath:w.store.path});let T=d();T.use(d.json({limit:`1mb`}));let E=Number(u),D=`http://localhost:${E}`,O=process.env.AIKIT_CORS_ORIGIN??D,k=process.env.AIKIT_ALLOW_ANY_ORIGIN===`true`,A=n(`AIKIT_HTTP_MAX_SESSIONS`,8),j=n(`AIKIT_HTTP_SESSION_TIMEOUT_MINUTES`,30),M=n(`AIKIT_HTTP_SESSION_GC_INTERVAL_MINUTES`,5),N=t({limit:100,windowMs:6e4}),P=!1;T.use((e,t,n)=>{let i=Array.isArray(e.headers.origin)?e.headers.origin[0]:e.headers.origin,a=r({requestOrigin:i,configuredOrigin:O,allowAnyOrigin:k,fallbackOrigin:D});if(a.warn&&!P&&(P=!0,_.warn(`Rejected non-local CORS origin while AIKIT_CORS_ORIGIN=*`,{origin:i,allowAnyOriginEnv:`AIKIT_ALLOW_ANY_ORIGIN=true`})),i&&!a.allowOrigin){t.status(403).json({error:`Origin not allowed`});return}if(a.allowOrigin&&t.setHeader(`Access-Control-Allow-Origin`,a.allowOrigin),t.setHeader(`Access-Control-Allow-Methods`,`GET, POST, PUT, PATCH, DELETE, OPTIONS`),t.setHeader(`Access-Control-Allow-Headers`,`Content-Type, Authorization, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID`),t.setHeader(`Access-Control-Expose-Headers`,`Mcp-Session-Id`),e.method===`OPTIONS`){t.status(204).end();return}n()});let F=C();T.use(S(F)),T.use(`/mcp`,(t,n,r)=>{let i=e(t)??t.ip??t.socket.remoteAddress??`anonymous`;if(N.allow(i)){r();return}let a=Math.max(1,Math.ceil(N.getRetryAfterMs(i)/1e3));n.setHeader(`Retry-After`,String(a)),n.status(429).json({jsonrpc:`2.0`,error:{code:-32003,message:`Rate limit exceeded`},id:null})});let I;T.use(`/mcp`,(e,t,n)=>{if(!I){let t=e.headers[`x-workspace-root`];typeof t==`string`&&t.length>0&&(I=t,_.debug(`Captured workspace root from proxy header`,{wsRoot:t}))}n()}),h(T,v(),_);let L=new Date().toISOString();T.use(`/settings/api`,x({log:_,mcpInfo:()=>({transport:`http`,port:E,pid:process.pid,startedAt:L})})),y(T,b(),_),T.get(`/health`,(e,t)=>{t.json({status:`ok`})});let R=!1,z=null,B=null,V=null,H=null,U=null,W=null,G=null,K=null,q=Promise.resolve(),J=async(t,n)=>{if(!R||!V||!H){n.status(503).json({jsonrpc:`2.0`,error:{code:-32603,message:`Server initializing — please retry in a few seconds`},id:null});return}let r=q,i;q=new Promise(e=>{i=e});let a={POST:6e4,GET:1e4,DELETE:1e4}[t.method]??3e4;if(!await Promise.race([r.then(()=>!0),new Promise(e=>{let n=setTimeout(()=>{_.warn(`mcpLock timed out waiting for previous request, resetting lock chain`,{method:t.method,timeoutMs:a}),e(!1)},a);n.unref&&n.unref()})])&&(q=Promise.resolve(),i=()=>{},W)){let e=W;W=null,G=null,e.close().catch(()=>{})}try{let r=e(t);if(!W){if(r){n.status(404).json({jsonrpc:`2.0`,error:{code:-32001,message:`Session not found`},id:null});return}let e=new H({sessionIdGenerator:()=>l(),onsessioninitialized:async e=>{G=e,B?.onSessionStart(e,{transport:`http`})},onsessionclosed:async e=>{e&&B?.onSessionEnd(e),G=null}});e.onclose=()=>{W===e&&(W=null),G===e.sessionId&&(G=null)},W=e,await V.connect(e)}let i=W;await i.handleRequest(t,n,t.body),t.method!==`DELETE`&&(!r&&i.sessionId?(G=i.sessionId,B?.onSessionStart(i.sessionId,{transport:`http`}),B?.onSessionActivity(i.sessionId)):r&&B?.onSessionActivity(r))}catch(e){if(_.error(`MCP handler error`,s(e)),!n.headersSent){let t=e instanceof Error?e.message:String(e),r=t.includes(`Not Acceptable`);n.status(r?406:500).json({jsonrpc:`2.0`,error:{code:r?-32e3:-32603,message:r?t:`Internal server error`},id:null})}}finally{i()}},Y=async(t,n)=>{let r=e(t);if(U&&(!W||r!==G)){await U.handleRequest(t,n,t.body);return}await J(t,n)};T.post(`/mcp`,Y),T.get(`/mcp`,Y),T.delete(`/mcp`,Y);let X=T.listen(E,`127.0.0.1`,()=>{_.info(`MCP server listening`,{url:`http://127.0.0.1:${E}/mcp`,port:E}),setTimeout(async()=>{try{typeof I==`string`&&I.length>0&&(w.sources[0]={path:I,excludePatterns:w.sources[0]?.excludePatterns??[]},w.allRoots=[I],_.debug(`Workspace root applied from proxy header`,{wsRoot:I}));let[{createLazyServer:e,createMcpServer:t,ALL_TOOL_NAMES:n},{StreamableHTTPServerTransport:r},{checkForUpdates:c,autoUpgradeScaffold:l}]=await Promise.all([import(`./server-CICqmfQ_.js`),import(`@modelcontextprotocol/sdk/server/streamableHttp.js`),import(`./version-check-CZ5hY6R2.js`)]);c(),l(),setInterval(c,1440*60*1e3).unref();let u=!1,d=p(w),f=e(w,d);K=async()=>{f.aikit&&await Promise.all([f.aikit.embedder.shutdown?.().catch(()=>{})??Promise.resolve(),f.aikit.graphStore.close().catch(()=>{}),f.aikit.closeStateStore?.().catch(()=>{})??Promise.resolve(),f.aikit.store.close().catch(()=>{})])},V=f.server,H=r,R=!0,_.debug(`MCP server configured (lazy — AI Kit initializing in background)`,{toolCount:n.length,resourceCount:2}),f.startInit(),f.ready.then(async()=>{try{if(!f.aikit)throw Error(`AI Kit components are not available after initialization`);B=new g(f.aikit.stateStore,{staleTimeoutMinutes:j,gcIntervalMinutes:M,onBeforeSessionDelete:e=>{if(G===e&&W){let e=W;W=null,G=null,e.close().catch(()=>void 0)}U?.closeSession(e,{notifySessionEnd:!1})},onSessionEndMaintenance:async()=>{if(!f.aikit?.curated||!f.aikit?.stateStore)return;let{pruneLessons:e}=await import(`./evolution-DWaEE6XW.js`).then(e=>e.t),t=await e(f.aikit.curated,f.aikit.stateStore,{dryRun:!1});t.pruned.length>0&&_.info(`Session-end lesson prune`,{pruned:t.pruned.length})}}),U=new m({createServer:()=>{if(!f.aikit)throw Error(`AI Kit components are not available after initialization`);return t(f.aikit,w)},createTransport:e=>new r(e),maxSessions:A,sessionTimeoutMinutes:j,onSessionStart:e=>B?.onSessionStart(e,{transport:`http`}),onSessionActivity:e=>B?.onSessionActivity(e),onSessionEnd:e=>B?.onSessionEnd(e)}),B.startGC(),G&&(B.onSessionStart(G,{transport:`http`}),B.onSessionActivity(G)),_.info(`HTTP session runtime ready`,{maxSessions:A,sessionTimeoutMinutes:j,gcIntervalMinutes:M})}catch(e){_.error(`Failed to start session manager`,s(e)),R=!1,u=!0,V=null,H=null,K=null}}),d===`auto`?f.ready.then(async()=>{try{let e=w.sources.map(e=>e.path).join(`, `);_.info(`Running initial index`,{sourcePaths:e}),await f.runInitialIndex(),_.info(`Initial index complete`)}catch(e){_.error(`Initial index failed; will retry on aikit_reindex`,i(o,e))}}):d===`smart`?u||f.ready.then(async()=>{try{if(!f.aikit)throw Error(`AI Kit components are not available after initialization`);let{SmartIndexScheduler:e}=await import(`../../indexer/dist/index.js`),t=new e(f.aikit.indexer,w,f.aikit.store),n=f.aikit.store;z=t,t.start(),n.onBeforeClose&&n.onBeforeClose(()=>t.stop()),f.setSmartScheduler(t),_.debug(`Smart index scheduler started (HTTP mode)`)}catch(e){_.error(`Failed to start smart index scheduler`,i(o,e))}}):_.info(`Initial full indexing skipped in HTTP mode`,{indexMode:d}),f.ready.catch(e=>{_.error(`AI Kit initialization failed`,i(o,e)),R=!1,u=!0,V=null,H=null,K=null}),a(f.ready,()=>f.aikit?{curated:f.aikit.curated,stateStore:f.aikit.stateStore}:null,o)}catch(e){_.error(`Failed to load server modules`,i(o,e)),R=!1,K=null}},100)}),Z=!1,Q=async e=>{Z||(Z=!0,_.info(`Shutdown signal received`,{signal:e}),z?.stop(),B?.stop(),await import(`../../tools/dist/index.js`).then(({processStopAll:e})=>e()).catch(()=>{}),await U?.closeAll().catch(()=>void 0),G&&B?.onSessionEnd(G),W&&(await W.close().catch(()=>void 0),W=null,G=null),X.close(),V&&await V.close(),await K?.().catch(()=>void 0),process.exit(0))};process.on(`SIGINT`,()=>Q(`SIGINT`)),process.on(`SIGTERM`,()=>Q(`SIGTERM`))}export{v as startHttpMode};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./workspace-bootstrap-B57Oz40_.js";import{n as t,t as n}from"./startup-maintenance-DX1yTfBa.js";import{t as r}from"./repair-json-B6Q_HRoP.js";import{createLogger as i,serializeError as a,setDetailedErrorLoggingEnabled as o}from"../../core/dist/index.js";const s=i(`server`);var c=class{buffer;append(e){this.buffer=this.buffer?Buffer.concat([this.buffer,e]):e}readMessage(){if(!this.buffer)return null;for(let e=0;e<this.buffer.length;e++){if(this.buffer[e]!==10)continue;let t=this.buffer.toString(`utf8`,0,e).replace(/\r$/,``),n=e+1;try{let e=JSON.parse(t);return this.buffer=this.buffer.subarray(n),e}catch{try{let e=r(t);return s.warn(`JSON parse failed on MCP message — repair succeeded`,{preview:t.slice(0,80)}),this.buffer=this.buffer.subarray(n),e}catch{}}}return null}clear(){this.buffer=void 0}};async function l(r){let[{loadConfig:i,reconfigureForWorkspace:l,resolveIndexMode:u},{createLazyServer:d},{checkForUpdates:f,autoUpgradeScaffold:p},{RootsListChangedNotificationSchema:m}]=await Promise.all([import(`./config-BXd38nVn.js`),import(`./server-
|
|
1
|
+
import{n as e}from"./workspace-bootstrap-B57Oz40_.js";import{n as t,t as n}from"./startup-maintenance-DX1yTfBa.js";import{t as r}from"./repair-json-B6Q_HRoP.js";import{createLogger as i,serializeError as a,setDetailedErrorLoggingEnabled as o}from"../../core/dist/index.js";const s=i(`server`);var c=class{buffer;append(e){this.buffer=this.buffer?Buffer.concat([this.buffer,e]):e}readMessage(){if(!this.buffer)return null;for(let e=0;e<this.buffer.length;e++){if(this.buffer[e]!==10)continue;let t=this.buffer.toString(`utf8`,0,e).replace(/\r$/,``),n=e+1;try{let e=JSON.parse(t);return this.buffer=this.buffer.subarray(n),e}catch{try{let e=r(t);return s.warn(`JSON parse failed on MCP message — repair succeeded`,{preview:t.slice(0,80)}),this.buffer=this.buffer.subarray(n),e}catch{}}}return null}clear(){this.buffer=void 0}};async function l(r){let[{loadConfig:i,reconfigureForWorkspace:l,resolveIndexMode:u},{createLazyServer:d},{checkForUpdates:f,autoUpgradeScaffold:p},{RootsListChangedNotificationSchema:m}]=await Promise.all([import(`./config-BXd38nVn.js`),import(`./server-BZVPmRGV.js`),import(`./version-check-B6bui-YI.js`),import(`@modelcontextprotocol/sdk/types.js`)]),h=i();o(h.logging?.errorDetails===!0),h.configError&&s.warn(`Config load failure`,{error:h.configError}),s.info(`Config loaded`,{sourceCount:h.sources.length,storePath:h.store.path}),p(),setInterval(f,1440*60*1e3).unref();let g=u(h),_=d(h,g),{server:v,startInit:y,ready:b,runInitialIndex:x}=_,{StdioServerTransport:S}=await import(`@modelcontextprotocol/sdk/server/stdio.js`),C=new S;if(typeof C._readBuffer?.readMessage!=`function`)throw Error(`Cannot install JSON repair: StdioServerTransport._readBuffer has unexpected shape`);C._readBuffer=new c,s.debug(`JSON repair installed on stdio transport`),await v.connect(C),s.debug(`MCP server started`,{transport:`stdio`}),await e({config:h,indexMode:g,log:s,rootsChangedNotificationSchema:m,reconfigureForWorkspace:l,runInitialIndex:x,server:v,startInit:y,version:r});let w=null,T=null,E=!1,D=async e=>{E||(E=!0,s.info(`Shutdown signal received`,{signal:e}),w&&=(clearTimeout(w),null),T?.stop(),await import(`../../tools/dist/index.js`).then(({processStopAll:e})=>e()).catch(()=>{}),await C.close().catch(()=>void 0),await v.close().catch(()=>void 0),_.aikit&&await Promise.all([_.aikit.embedder.shutdown?.().catch(()=>{})??Promise.resolve(),_.aikit.graphStore.close().catch(()=>{}),_.aikit.closeStateStore?.().catch(()=>{})??Promise.resolve(),_.aikit.store.close().catch(()=>{})]),process.exit(0))},O=()=>{w&&clearTimeout(w),w=setTimeout(async()=>{s.info(`Auto-shutdown: no activity for 30 minutes — releasing resources`);try{let e=_.aikit;e&&(e.embedder.shutdown?.().catch(()=>{}),e.store.releaseMemory?.(),e.graphStore.releaseMemory?.())}catch(e){s.warn(`Resource release failed during shutdown`,a(e))}},18e5),w.unref&&w.unref()};O(),process.stdin.on(`data`,()=>O()),process.stdin.on(`end`,()=>void D(`stdin-end`)),process.stdin.on(`close`,()=>void D(`stdin-close`)),process.stdin.on(`error`,()=>void D(`stdin-error`)),process.on(`SIGINT`,()=>void D(`SIGINT`)),process.on(`SIGTERM`,()=>void D(`SIGTERM`)),b.catch(e=>{s.error(`Initialization failed — server will continue with limited tools`,t(r,e))}),g===`smart`?b.then(async()=>{try{if(!_.aikit)throw Error(`AI Kit components are not available after initialization`);let{SmartIndexScheduler:e}=await import(`../../indexer/dist/index.js`),t=new e(_.aikit.indexer,h,_.aikit.store);T=t;let n=_.aikit.store;t.start(),n.onBeforeClose&&n.onBeforeClose(()=>t.stop()),_.setSmartScheduler(t),s.debug(`Smart index scheduler started (stdio mode)`)}catch(e){s.error(`Failed to start smart index scheduler`,t(r,e))}}).catch(e=>s.error(`AI Kit init failed for smart scheduler`,t(r,e))):s.warn(`Initial full indexing skipped; use aikit_reindex to index manually`,{indexMode:g}),n(b,()=>_.aikit?{curated:_.aikit.curated,stateStore:_.aikit.stateStore}:null,r)}export{l as startStdioMode};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{r as e}from"./bin.js";import{n as t,t as n}from"./startup-maintenance-CBJWiJ7p.js";import{t as r}from"./repair-json-D4mft_HA.js";import{createLogger as i,serializeError as a,setDetailedErrorLoggingEnabled as o}from"../../core/dist/index.js";const s=i(`server`);var c=class{buffer;append(e){this.buffer=this.buffer?Buffer.concat([this.buffer,e]):e}readMessage(){if(!this.buffer)return null;for(let e=0;e<this.buffer.length;e++){if(this.buffer[e]!==10)continue;let t=this.buffer.toString(`utf8`,0,e).replace(/\r$/,``),n=e+1;try{let e=JSON.parse(t);return this.buffer=this.buffer.subarray(n),e}catch{try{let e=r(t);return s.warn(`JSON parse failed on MCP message — repair succeeded`,{preview:t.slice(0,80)}),this.buffer=this.buffer.subarray(n),e}catch{}}}return null}clear(){this.buffer=void 0}};async function l(r){let[{loadConfig:i,reconfigureForWorkspace:l,resolveIndexMode:u},{createLazyServer:d},{checkForUpdates:f,autoUpgradeScaffold:p},{RootsListChangedNotificationSchema:m}]=await Promise.all([import(`./config-4M54ZAT8.js`),import(`./server-
|
|
2
|
+
import{r as e}from"./bin.js";import{n as t,t as n}from"./startup-maintenance-CBJWiJ7p.js";import{t as r}from"./repair-json-D4mft_HA.js";import{createLogger as i,serializeError as a,setDetailedErrorLoggingEnabled as o}from"../../core/dist/index.js";const s=i(`server`);var c=class{buffer;append(e){this.buffer=this.buffer?Buffer.concat([this.buffer,e]):e}readMessage(){if(!this.buffer)return null;for(let e=0;e<this.buffer.length;e++){if(this.buffer[e]!==10)continue;let t=this.buffer.toString(`utf8`,0,e).replace(/\r$/,``),n=e+1;try{let e=JSON.parse(t);return this.buffer=this.buffer.subarray(n),e}catch{try{let e=r(t);return s.warn(`JSON parse failed on MCP message — repair succeeded`,{preview:t.slice(0,80)}),this.buffer=this.buffer.subarray(n),e}catch{}}}return null}clear(){this.buffer=void 0}};async function l(r){let[{loadConfig:i,reconfigureForWorkspace:l,resolveIndexMode:u},{createLazyServer:d},{checkForUpdates:f,autoUpgradeScaffold:p},{RootsListChangedNotificationSchema:m}]=await Promise.all([import(`./config-4M54ZAT8.js`),import(`./server-CICqmfQ_.js`),import(`./version-check-CZ5hY6R2.js`),import(`@modelcontextprotocol/sdk/types.js`)]),h=i();o(h.logging?.errorDetails===!0),h.configError&&s.warn(`Config load failure`,{error:h.configError}),s.info(`Config loaded`,{sourceCount:h.sources.length,storePath:h.store.path}),p(),setInterval(f,1440*60*1e3).unref();let g=u(h),_=d(h,g),{server:v,startInit:y,ready:b,runInitialIndex:x}=_,{StdioServerTransport:S}=await import(`@modelcontextprotocol/sdk/server/stdio.js`),C=new S;if(typeof C._readBuffer?.readMessage!=`function`)throw Error(`Cannot install JSON repair: StdioServerTransport._readBuffer has unexpected shape`);C._readBuffer=new c,s.debug(`JSON repair installed on stdio transport`),await v.connect(C),s.debug(`MCP server started`,{transport:`stdio`}),await e({config:h,indexMode:g,log:s,rootsChangedNotificationSchema:m,reconfigureForWorkspace:l,runInitialIndex:x,server:v,startInit:y,version:r});let w=null,T=null,E=!1,D=async e=>{E||(E=!0,s.info(`Shutdown signal received`,{signal:e}),w&&=(clearTimeout(w),null),T?.stop(),await import(`../../tools/dist/index.js`).then(({processStopAll:e})=>e()).catch(()=>{}),await C.close().catch(()=>void 0),await v.close().catch(()=>void 0),_.aikit&&await Promise.all([_.aikit.embedder.shutdown?.().catch(()=>{})??Promise.resolve(),_.aikit.graphStore.close().catch(()=>{}),_.aikit.closeStateStore?.().catch(()=>{})??Promise.resolve(),_.aikit.store.close().catch(()=>{})]),process.exit(0))},O=()=>{w&&clearTimeout(w),w=setTimeout(async()=>{s.info(`Auto-shutdown: no activity for 30 minutes — releasing resources`);try{let e=_.aikit;e&&(e.embedder.shutdown?.().catch(()=>{}),e.store.releaseMemory?.(),e.graphStore.releaseMemory?.())}catch(e){s.warn(`Resource release failed during shutdown`,a(e))}},18e5),w.unref&&w.unref()};O(),process.stdin.on(`data`,()=>O()),process.stdin.on(`end`,()=>void D(`stdin-end`)),process.stdin.on(`close`,()=>void D(`stdin-close`)),process.stdin.on(`error`,()=>void D(`stdin-error`)),process.on(`SIGINT`,()=>void D(`SIGINT`)),process.on(`SIGTERM`,()=>void D(`SIGTERM`)),b.catch(e=>{s.error(`Initialization failed — server will continue with limited tools`,t(r,e))}),g===`smart`?b.then(async()=>{try{if(!_.aikit)throw Error(`AI Kit components are not available after initialization`);let{SmartIndexScheduler:e}=await import(`../../indexer/dist/index.js`),t=new e(_.aikit.indexer,h,_.aikit.store);T=t;let n=_.aikit.store;t.start(),n.onBeforeClose&&n.onBeforeClose(()=>t.stop()),_.setSmartScheduler(t),s.debug(`Smart index scheduler started (stdio mode)`)}catch(e){s.error(`Failed to start smart index scheduler`,t(r,e))}}).catch(e=>s.error(`AI Kit init failed for smart scheduler`,t(r,e))):s.warn(`Initial full indexing skipped; use aikit_reindex to index manually`,{indexMode:g}),n(b,()=>_.aikit?{curated:_.aikit.curated,stateStore:_.aikit.stateStore}:null,r)}export{l as startStdioMode};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{EXEC_HOOKS as e,HOOK_EVENTS as t,HOOK_TOOL_MATCHERS as n,SUPPORTED_PLATFORMS as r}from"../definitions/exec-hooks.mjs";const i={PreCompact:3e3,PostToolUse:3e3},a={copilot:`hooks.json`,copilotCli:`hooks.json`,hermes:`hooks.json`},o=r;function s(e){if(!o.includes(e))throw Error(`Unknown platform: "${e}". Supported: ${o.join(`, `)}. Add it to SUPPORTED_PLATFORMS in exec-hooks.mjs first, then add entries to HOOK_EVENTS and HOOK_TOOL_MATCHERS. Add FILE_NAMES in generateHooks if file-producing.`);for(let[n,r]of Object.entries(t))if(!(e in r))throw Error(`Platform "${e}" is missing from HOOK_EVENTS.${n}. Add a "${e}" key to HOOK_EVENTS.${n} in exec-hooks.mjs.`);for(let[t,r]of Object.entries(n))if(!(e in r))throw Error(`Platform "${e}" is missing from HOOK_TOOL_MATCHERS.${t}. Add a "${e}" key to HOOK_TOOL_MATCHERS.${t} in exec-hooks.mjs.`)}function c(e,t){return(e.matcher||[]).flatMap(e=>{let r=n[e];if(!r)throw Error(`Unknown hook matcher: ${e}`);return r[t]||[]})}function l(e,t,n,r){let a=`${n}/${e.script}`;
|
|
1
|
+
import{EXEC_HOOKS as e,HOOK_EVENTS as t,HOOK_TOOL_MATCHERS as n,SUPPORTED_PLATFORMS as r}from"../definitions/exec-hooks.mjs";const i={PreCompact:3e3,PostToolUse:3e3},a={copilot:`hooks.json`,copilotCli:`hooks.json`,hermes:`hooks.json`},o=r;function s(e){if(!o.includes(e))throw Error(`Unknown platform: "${e}". Supported: ${o.join(`, `)}. Add it to SUPPORTED_PLATFORMS in exec-hooks.mjs first, then add entries to HOOK_EVENTS and HOOK_TOOL_MATCHERS. Add FILE_NAMES in generateHooks if file-producing.`);for(let[n,r]of Object.entries(t))if(!(e in r))throw Error(`Platform "${e}" is missing from HOOK_EVENTS.${n}. Add a "${e}" key to HOOK_EVENTS.${n} in exec-hooks.mjs.`);for(let[t,r]of Object.entries(n))if(!(e in r))throw Error(`Platform "${e}" is missing from HOOK_TOOL_MATCHERS.${t}. Add a "${e}" key to HOOK_TOOL_MATCHERS.${t} in exec-hooks.mjs.`)}function c(e,t){return(e.matcher||[]).flatMap(e=>{let r=n[e];if(!r)throw Error(`Unknown hook matcher: ${e}`);return r[t]||[]})}function l(e,t,n,r){let a=`${n}/${e.script}`;if(r===`claude`){let t=c(e,r);return{matcher:t.length>0?t.join(`|`):``,hooks:[{type:`command`,command:`node ${JSON.stringify(a)}`}]}}return r===`copilot`?{event:t,steps:[{type:`command`,command:`node`,args:[a],timeout:i[e.event]||5e3}]}:{command:`node`,args:[a]}}function u(n,r){s(n);let i={};for(let a of Object.values(e)){let e=t[a.event]?.[n];if(!e){console.warn(`[aikit] Unsupported hook event ${a.event} for ${n} — skipping`);continue}i[e]||=[],i[e].push(l(a,e,r,n))}return{hooks:i}}function d(n,r){if(s(n),n===`claude`)return[];if(n===`copilot`){let i=Object.values(e).flatMap(e=>{let i=t[e.event]?.[n];return i?l(e,i,r,n):(console.warn(`[aikit] Unsupported hook event ${e.event} for ${n} — skipping`),[])});return[{path:a[n],content:JSON.stringify({hooks:i},null,2)}]}if(!(n in a))throw Error(`Platform "${n}" is missing from FILE_NAMES. Add "${n}: '<filename>'" to FILE_NAMES in adapters/hooks.mjs.`);let i={};for(let a of Object.values(e)){let e=t[a.event]?.[n];if(!e){console.warn(`[aikit] Unsupported hook event ${a.event} for ${n} — skipping`);continue}i[e]||=[],i[e].push(l(a,e,r,n))}return[{path:a[n],content:JSON.stringify({hooks:i},null,2)}]}function f(){return[`_runtime.mjs`,...Object.values(e).map(e=>e.script)]}export{u as buildHooksBlock,d as generateHooks,f as getHookScriptFiles};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=[`copilot`,`claude`,`copilotCli`,`hermes`],t={SessionStart:{copilot:`SessionStart`,claude:`SessionStart`,copilotCli:`sessionStart`,hermes:`SessionStart`},PreToolUse:{copilot:`PreToolUse`,claude:`PreToolUse`,copilotCli:`preToolUse`,hermes:`PreToolUse`},PostToolUse:{copilot:`PostToolUse`,claude:`PostToolUse`,copilotCli:`postToolUse`,hermes:`PostToolUse`},SubagentStart:{copilot:`SubagentStart`,claude:`SubagentStart`,copilotCli:`subagentStart`,hermes:`SubagentStart`},PreCompact:{copilot:`PreCompact`,claude:`PreCompact`,copilotCli:`preCompact`,hermes:`PreCompact`},Stop:{copilot:`Stop`,claude:`Stop`,copilotCli:`stop`,hermes:`Stop`}},n={fileRead:{copilot:[`read_file`,`readFile`],claude:[`Read`],copilotCli:[`read_file`],hermes:[`read_file`]},fileWrite:{copilot:[`editFiles`,`replace_string_in_file`,`create_file`],claude:[`Write`,`
|
|
1
|
+
const e=[`copilot`,`claude`,`copilotCli`,`hermes`],t={SessionStart:{copilot:`SessionStart`,claude:`SessionStart`,copilotCli:`sessionStart`,hermes:`SessionStart`},PreToolUse:{copilot:`PreToolUse`,claude:`PreToolUse`,copilotCli:`preToolUse`,hermes:`PreToolUse`},PostToolUse:{copilot:`PostToolUse`,claude:`PostToolUse`,copilotCli:`postToolUse`,hermes:`PostToolUse`},SubagentStart:{copilot:`SubagentStart`,claude:`SubagentStart`,copilotCli:`subagentStart`,hermes:`SubagentStart`},PreCompact:{copilot:`PreCompact`,claude:`PreCompact`,copilotCli:`preCompact`,hermes:`PreCompact`},Stop:{copilot:`Stop`,claude:`Stop`,copilotCli:`stop`,hermes:`Stop`}},n={fileRead:{copilot:[`read_file`,`readFile`],claude:[`Read`],copilotCli:[`read_file`],hermes:[`read_file`]},fileWrite:{copilot:[`editFiles`,`replace_string_in_file`,`create_file`],claude:[`Edit`,`Write`,`MultiEdit`],copilotCli:[`editFiles`,`replace_string_in_file`],hermes:[`write_file`,`patch`]},fileSearch:{copilot:[`grep_search`,`semantic_search`,`find`],claude:[`Bash`],copilotCli:[`grep_search`],hermes:[`search_files`]}},r={"privacy-guard":{id:`privacy-guard`,event:`PreToolUse`,category:`guard`,description:`Deny reads of secret-bearing files: .env, .pem, .key, .ssh/*, credentials, tokens, .netrc.`,script:`privacy-guard.mjs`,matcher:[`fileRead`],outputBudgetLines:0,scope:`user`},"scout-guard":{id:`scout-guard`,event:`PreToolUse`,category:`guard`,description:`Deny reads inside generated/dependency directories: node_modules/, dist/, .git/objects/, vendor/, build/.`,script:`scout-guard.mjs`,matcher:[`fileRead`,`fileSearch`],outputBudgetLines:0,scope:`user`},"freshness-signal":{id:`freshness-signal`,event:`SessionStart`,category:`signal`,description:`Check workspace state freshness at session start: onboard staleness, active flow, scaffold drift.`,script:`freshness-signal.mjs`,outputBudgetLines:5,scope:`user`},"scope-guard":{id:`scope-guard`,event:`PostToolUse`,category:`signal`,description:`After file writes, detect edit drift: files changed outside flow scope, too many files, scaffold source/dist mismatch.`,script:`scope-guard.mjs`,matcher:[`fileWrite`],outputBudgetLines:5,scope:`user`}};function i(r){let i=[{name:`HOOK_EVENTS`,entries:Object.values(t)},{name:`HOOK_TOOL_MATCHERS`,entries:Object.values(n)}];for(let t of e){for(let e of i)for(let n of e.entries)if(!(t in n))throw Error(`Registration drift: platform "${t}" is missing from ${e.name} (entry has keys: ${Object.keys(n).join(`, `)}). Add a "${t}" key to all entries in ${e.name} in exec-hooks.mjs.`);if(r){for(let[e,n]of Object.entries(r))if(!(t in n))throw Error(`Registration drift: platform "${t}" is missing from ${e} (has keys: ${Object.keys(n).join(`, `)}). Add "${t}: '...'" to ${e}.`)}}}export{r as EXEC_HOOKS,t as HOOK_EVENTS,n as HOOK_TOOL_MATCHERS,e as SUPPORTED_PLATFORMS,i as validateAllPlatformRegistries};
|
|
@@ -24,23 +24,24 @@ ${e===`<PROFILE>`?`**Profile:** Check your role → implementer | documenter | r
|
|
|
24
24
|
|
|
25
25
|
AI Kit delivers knowledge through four layers:
|
|
26
26
|
|
|
27
|
-
- **L0 — Generated Briefing Cards**: Fast, task-ready workspace setup. \`workspace-core\` available as local resource \`aikit://l0/workspace-core\` (full card) and via \`status({ includePrelude: true })\` (compact summary), both populated after \`onboard({ path, mode: 'generate' })\` runs for the workspace. If workspaceCore null → run onboard first. If a flow is active, \`workflow:<flow>\` loaded via flow context (replaces task card). Total budget: ${n.sessionStart.totalTokenBudget} tokens.
|
|
27
|
+
- **L0 — Generated Briefing Cards**: Fast, task-ready workspace setup. \`workspace-core\` available as local resource \`aikit://l0/workspace-core\` (full card) and via \`status({ includePrelude: true })\` (compact summary), both populated after \`onboard({ path, mode: 'generate' })\` runs for the workspace. Treat this card as already-loaded workspace context: consume it before broad \`search\`, \`scope_map\`, \`digest\`, or repeated \`file_summary\`. If workspaceCore null → run onboard first. If a flow is active, \`workflow:<flow>\` loaded via flow context (replaces task card). Total budget: ${n.sessionStart.totalTokenBudget} tokens.
|
|
28
28
|
- **L1 — Flow Working Context**: Active run state, current step, and role-filtered dispatch context. Withdraw via \`knowledge({ action: 'withdraw', scope: 'flow', profile: '<role>', budget: 6000 })\`.
|
|
29
29
|
- **L2 — Canonical Curated Knowledge**: Durable workspace archive managed by CuratedKnowledgeManager. Retrieve on demand via \`search()\` or \`knowledge({ action: 'read' })\`.
|
|
30
30
|
- **L3 — Evidence Archive**: Auto-knowledge observations and tool-output evidence. Inspect only for provenance, conflict review, or promotion assessment. Never auto-injected.
|
|
31
31
|
|
|
32
32
|
### Hot Path
|
|
33
|
-
1. Session start → fetch \`aikit://l0/workspace-core\` resource (full card) or \`status({ includePrelude: true })\` (compact summary). Prerequisite: \`onboard({ path, mode: 'generate' })\` must have run for the workspace. If workspaceCore null → run onboard first, then status again.
|
|
34
|
-
2.
|
|
35
|
-
3.
|
|
36
|
-
4.
|
|
33
|
+
1. Session start → fetch \`aikit://l0/workspace-core\` resource (full card) or \`status({ includePrelude: true })\` (compact summary). Prerequisite: \`onboard({ path, mode: 'generate' })\` must have run for the workspace. If workspaceCore null → run onboard first, then status again.
|
|
34
|
+
2. Before broad repo scans, extract and reuse the L0 facts: workspace shape, package map, tool surfaces, key entry points, constraints, and known pitfalls. If L0 answers enough, skip \`scope_map\`/wide \`search\` and go straight to task-specific retrieval.
|
|
35
|
+
3. If flow active, \`workflow:<flow>\` loaded via flow context; use L1 snapshot + dispatch refs before broad retrieval. If no flow, may add one task card via \`file_summary\`.
|
|
36
|
+
4. Retrieve L2 on demand.
|
|
37
|
+
5. Inspect L3 only for evidence, provenance, or promotion.
|
|
37
38
|
|
|
38
39
|
### No-Flow Fallback
|
|
39
40
|
- Keep L1 absent; workspace-core available via status after onboard; optionally one task card.
|
|
40
41
|
- L2 on demand; turn-local scratch, no L1 synthesis.
|
|
41
42
|
|
|
42
43
|
### Degraded Fallback
|
|
43
|
-
- Missing L0 card
|
|
44
|
+
- Missing L0 card after regeneration → use \`status({ includePrelude: true })\` as compact fallback, then only task-specific L2 retrieval; do not replace L0 with a workspace-wide scan.
|
|
44
45
|
- Unavailable L1 → read persisted FlowRunMeta without mutation.
|
|
45
46
|
- Unavailable indexed L2 → read canonical curated markdown directly.
|
|
46
47
|
- Mark missing evidence unresolved; block promotion.
|
|
@@ -19,10 +19,10 @@ Use AI Kit as compression, memory, validation, and coordination layer. Each call
|
|
|
19
19
|
Start:
|
|
20
20
|
1. status({ includePrelude: true }).
|
|
21
21
|
2. If prelude.workspaceCore is null → run \`onboard({ path, mode: 'generate' })\`, then \`status({ includePrelude: true })\` again.
|
|
22
|
-
3. **L0 session start**: Fetch \`aikit://l0/workspace-core\` resource (full card) or \`status({ includePrelude: true })\` (compact summary). If a flow is active, workflow card loaded via flow context. ≤ 2,100 tokens.
|
|
22
|
+
3. **L0 session start**: Fetch \`aikit://l0/workspace-core\` resource (full card) or \`status({ includePrelude: true })\` (compact summary). Extract workspace shape, package map, tool surfaces, key entry points, constraints, and known pitfalls before broad retrieval. If L0 answers enough, skip \`scope_map\`/wide \`search\`. If a flow is active, workflow card loaded via flow context. ≤ 2,100 tokens.
|
|
23
23
|
4. search({ query: "SESSION CHECKPOINT", origin: "curated" }).
|
|
24
24
|
5. flow({ action: 'status' }) when active flow may exist — use L1 snapshot before broad retrieval.
|
|
25
|
-
6. scope_map({ task })
|
|
25
|
+
6. scope_map({ task }) only when L0 + checkpoint do not identify a narrow workset.
|
|
26
26
|
|
|
27
27
|
During:
|
|
28
28
|
- Prefer search/file_summary/digest/symbol/trace/graph over raw reads.
|
|
@@ -40,7 +40,7 @@ End:
|
|
|
40
40
|
|
|
41
41
|
| Layer | Need | Tool |
|
|
42
42
|
|-------|------|------|
|
|
43
|
-
| **L0** | Session briefing (after onboard) | Resource \`aikit://l0/workspace-core\` (full card) or \`status({ includePrelude: true })\` (compact summary). Run \`onboard\` first if card missing. |
|
|
43
|
+
| **L0** | Session briefing (after onboard) | Resource \`aikit://l0/workspace-core\` (full card) or \`status({ includePrelude: true })\` (compact summary). Run \`onboard\` first if card missing. Consume before broad scans. |
|
|
44
44
|
| **L0** | Architecture overview | \`architecture\` briefing card |
|
|
45
45
|
| **L0** | Known issues | \`known-issues\` briefing card |
|
|
46
46
|
| **L0** | Flow-specific context | \`workflow:<flow>\` briefing card |
|
|
@@ -85,7 +85,7 @@ Review decayed/stale entries periodically with knowledge({ action: 'flagged' }).
|
|
|
85
85
|
Flows guide multi-step work. Read active step before acting. FORGE handles tiering/evidence/gates; load references only when main skill is too thin.
|
|
86
86
|
|
|
87
87
|
### Layered Knowledge Protocol
|
|
88
|
-
- **L0** → Generated briefing cards: load \`workspace-core\` at session start via \`aikit://l0/workspace-core\` resource (full card, live-updated) or \`status({ includePrelude: true })\` (compact summary). Requires \`onboard()\` first — if card null, run \`onboard\` before session start.
|
|
88
|
+
- **L0** → Generated briefing cards: load \`workspace-core\` at session start via \`aikit://l0/workspace-core\` resource (full card, live-updated) or \`status({ includePrelude: true })\` (compact summary). Requires \`onboard()\` first — if card null, run \`onboard\` before session start. Consume L0 as the default workspace map before \`search\`, \`scope_map\`, \`digest\`, or repeated \`file_summary\`; add \`workflow:<flow>\` if flow active, or one task card if no flow. Use \`file_summary({ tier: 'T1' })\` for structure, or \`file_summary({ tier: 'T2', query })\` for content only after L0 narrows the workset. Token-bounded.
|
|
89
89
|
- **L1** → Flow working context. Withdraw before dispatch; reuse before broad retrieval.
|
|
90
90
|
- **L2** → Canonical curated knowledge. Retrieve on demand; never preload broadly.
|
|
91
91
|
- **L3** → Evidence/auto-knowledge. Inspect only for provenance, conflict, or promotion assessment.
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import process from 'node:process';
|
|
3
3
|
|
|
4
|
+
/** Cross-platform event name aliases → canonical PascalCase. */
|
|
5
|
+
const EVENT_ALIASES = {
|
|
6
|
+
sessionstart: 'SessionStart',
|
|
7
|
+
pretooluse: 'PreToolUse',
|
|
8
|
+
posttooluse: 'PostToolUse',
|
|
9
|
+
subagentstart: 'SubagentStart',
|
|
10
|
+
precompact: 'PreCompact',
|
|
11
|
+
stop: 'Stop',
|
|
12
|
+
};
|
|
13
|
+
|
|
4
14
|
const FILE_KEYS = new Set([
|
|
5
15
|
'dirPath',
|
|
6
16
|
'file',
|
|
@@ -33,16 +43,56 @@ const normalizePath = (value) => {
|
|
|
33
43
|
};
|
|
34
44
|
|
|
35
45
|
/**
|
|
36
|
-
*
|
|
46
|
+
* Normalize event name from any platform casing to canonical PascalCase.
|
|
47
|
+
* "postToolUse", "post_tool_use", "PostToolUse" all → "PostToolUse".
|
|
48
|
+
*/
|
|
49
|
+
function normalizeEvent(rawEvent) {
|
|
50
|
+
const key = String(rawEvent ?? '')
|
|
51
|
+
.toLowerCase()
|
|
52
|
+
.replace(/[_-]/g, '');
|
|
53
|
+
return EVENT_ALIASES[key] ?? String(rawEvent ?? 'SessionStart');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Truncate a string to at most `maxLines` lines. Appends "(+N more)" if truncated.
|
|
58
|
+
* Returns empty string when input is empty or all-whitespace.
|
|
59
|
+
*/
|
|
60
|
+
function truncateOutput(text, maxLines) {
|
|
61
|
+
if (!text?.trim()) return '';
|
|
62
|
+
const lines = text.split('\n');
|
|
63
|
+
if (lines.length <= maxLines) return text;
|
|
64
|
+
return `${lines.slice(0, maxLines).join('\n')}\n(+${lines.length - maxLines} more)`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create a hook entrypoint that normalizes stdin and writes a platform-neutral response.
|
|
69
|
+
*
|
|
37
70
|
* @param {(context: ReturnType<typeof normalizeContext>) => Promise<object> | object} handler
|
|
71
|
+
* @param {object} [options]
|
|
72
|
+
* @param {string} [options.event] - If set, return allow for non-matching events (replaces per-script boilerplate).
|
|
73
|
+
* @param {'guard'|'signal'} [options.category] - 'guard' hooks deny on error; 'signal' hooks fail silent/allow.
|
|
74
|
+
* @param {number} [options.outputBudgetLines] - Max lines in additionalContext (0 = suppress all output).
|
|
38
75
|
*/
|
|
39
|
-
export async function createHook(handler) {
|
|
76
|
+
export async function createHook(handler, options = {}) {
|
|
40
77
|
try {
|
|
41
78
|
const raw = await readStdin();
|
|
42
|
-
const
|
|
43
|
-
|
|
79
|
+
const context = normalizeContext(raw);
|
|
80
|
+
|
|
81
|
+
// Event filter — silently allow non-matching events
|
|
82
|
+
if (options.event && context.event !== options.event) {
|
|
83
|
+
respond({ decision: 'allow' });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const result = (await handler(context)) ?? { decision: 'allow' };
|
|
88
|
+
const output = normalizeOutput(result, options);
|
|
89
|
+
respond(output);
|
|
44
90
|
} catch (error) {
|
|
45
|
-
|
|
91
|
+
const isGuard = options.category === 'guard';
|
|
92
|
+
respond({
|
|
93
|
+
decision: isGuard ? 'deny' : 'allow',
|
|
94
|
+
reason: isGuard ? `Hook error: ${error.message || error}` : undefined,
|
|
95
|
+
});
|
|
46
96
|
}
|
|
47
97
|
}
|
|
48
98
|
|
|
@@ -86,7 +136,7 @@ export function normalizeContext(raw = {}) {
|
|
|
86
136
|
return {
|
|
87
137
|
raw,
|
|
88
138
|
platform,
|
|
89
|
-
event: raw.event ?? raw.hookEvent ?? raw.hook_event ?? 'SessionStart',
|
|
139
|
+
event: normalizeEvent(raw.event ?? raw.hookEvent ?? raw.hook_event ?? 'SessionStart'),
|
|
90
140
|
toolName: raw.toolName ?? raw.tool_name ?? raw.tool ?? '',
|
|
91
141
|
toolInput,
|
|
92
142
|
filePaths: extractFilePaths(toolInput),
|
|
@@ -97,21 +147,43 @@ export function normalizeContext(raw = {}) {
|
|
|
97
147
|
};
|
|
98
148
|
}
|
|
99
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Normalize hook output: apply budget, suppress empty context, handle deny vs signal.
|
|
152
|
+
* @param {{ decision?: 'allow' | 'deny', reason?: string, additionalContext?: string, continue?: boolean }} result
|
|
153
|
+
* @param {{ outputBudgetLines?: number }} [options]
|
|
154
|
+
*/
|
|
155
|
+
export function normalizeOutput(result = {}, options = {}) {
|
|
156
|
+
const decision = result.decision ?? 'allow';
|
|
157
|
+
if (decision === 'deny') {
|
|
158
|
+
return { decision: 'deny', reason: result.reason ?? 'Hook denied request.' };
|
|
159
|
+
}
|
|
160
|
+
const budget = options.outputBudgetLines ?? 0;
|
|
161
|
+
const rawContext = result.additionalContext || '';
|
|
162
|
+
const context = budget > 0 ? truncateOutput(rawContext, budget) : rawContext;
|
|
163
|
+
const payload = {};
|
|
164
|
+
if (context) payload.additionalContext = context;
|
|
165
|
+
if (result.continue === false) payload.continue = false;
|
|
166
|
+
return payload;
|
|
167
|
+
}
|
|
168
|
+
|
|
100
169
|
/**
|
|
101
170
|
* Writes a normalized hook response to stdout and sets the exit code.
|
|
102
171
|
* @param {{ decision?: 'allow' | 'deny', reason?: string, additionalContext?: string, continue?: boolean }} result
|
|
103
172
|
*/
|
|
104
|
-
export function respond(result = {
|
|
173
|
+
export function respond(result = {}) {
|
|
105
174
|
const decision = result.decision ?? 'allow';
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
175
|
+
if (decision === 'deny') {
|
|
176
|
+
process.stdout.write(
|
|
177
|
+
`${JSON.stringify({ decision: 'deny', reason: result.reason ?? 'Hook denied request.' })}\n`,
|
|
178
|
+
);
|
|
179
|
+
process.exitCode = 2;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const payload = {};
|
|
183
|
+
if (result.additionalContext) payload.additionalContext = result.additionalContext;
|
|
184
|
+
if (result.continue === false) payload.continue = false;
|
|
113
185
|
if (Object.keys(payload).length > 0) process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
114
|
-
process.exitCode =
|
|
186
|
+
process.exitCode = 0;
|
|
115
187
|
}
|
|
116
188
|
|
|
117
189
|
/**
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* freshness-signal — SessionStart signal for workspace state freshness.
|
|
4
|
+
*
|
|
5
|
+
* Checks: onboard data exists, scaffold deployed, active flows.
|
|
6
|
+
* Returns silence when healthy. Signals only stale or missing state.
|
|
7
|
+
*
|
|
8
|
+
* @category signal
|
|
9
|
+
* @event SessionStart
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import { createHook } from './_runtime.mjs';
|
|
17
|
+
|
|
18
|
+
const AIKIT_WORKSPACES = path.join(os.homedir(), '.aikit', 'workspaces');
|
|
19
|
+
const DAY_MS = 86_400_000;
|
|
20
|
+
|
|
21
|
+
const exists = (p) => {
|
|
22
|
+
try {
|
|
23
|
+
return fs.existsSync(p);
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const readJson = (p) => {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check scaffold manifest at known deploy locations.
|
|
39
|
+
* Manifest is stored alongside the scaffold root, not in the workspace cwd.
|
|
40
|
+
*/
|
|
41
|
+
function checkScaffold(cwd) {
|
|
42
|
+
const candidates = [
|
|
43
|
+
path.join(cwd, '.github', '.aikit-scaffold.json'),
|
|
44
|
+
path.join(os.homedir(), '.claude', '.aikit-scaffold.json'),
|
|
45
|
+
path.join(os.homedir(), '.copilot', '.aikit-scaffold.json'),
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
for (const manifestPath of candidates) {
|
|
49
|
+
if (!exists(manifestPath)) continue;
|
|
50
|
+
const manifest = readJson(manifestPath);
|
|
51
|
+
if (!manifest) return { deployed: true, valid: false };
|
|
52
|
+
const installedAt = manifest.installedAt || manifest.generatedAt;
|
|
53
|
+
const age = installedAt ? Date.now() - new Date(installedAt).getTime() : null;
|
|
54
|
+
return {
|
|
55
|
+
deployed: true,
|
|
56
|
+
valid: true,
|
|
57
|
+
age,
|
|
58
|
+
stale: age !== null && age > 30 * DAY_MS,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { deployed: false };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Compute the same partition key as aikit's computePartitionKey.
|
|
67
|
+
* Partitions are ~/.aikit/workspaces/{basename}-{sha256(cwd).slice(0,8)}/
|
|
68
|
+
*
|
|
69
|
+
* Must match the canonical TypeScript implementation in global-registry.ts:
|
|
70
|
+
* resolve(cwd) → normalizePathForHash (lowercase on win32/darwin only)
|
|
71
|
+
* NO slash normalization — resolve produces native backslashes on Windows.
|
|
72
|
+
*/
|
|
73
|
+
function computePartitionKey(wsPath) {
|
|
74
|
+
const absPath = path.resolve(wsPath);
|
|
75
|
+
const name =
|
|
76
|
+
path
|
|
77
|
+
.basename(absPath)
|
|
78
|
+
.toLowerCase()
|
|
79
|
+
.replace(/[^a-z0-9-]/g, '-') || 'workspace';
|
|
80
|
+
const normalized =
|
|
81
|
+
process.platform === 'win32' || process.platform === 'darwin' ? absPath.toLowerCase() : absPath;
|
|
82
|
+
const hash = createHash('sha256').update(normalized).digest('hex').slice(0, 8);
|
|
83
|
+
return `${name}-${hash}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check workspace state for the specific partition matching this cwd.
|
|
88
|
+
* Returns state when workspace-core.md exists. Returns { partition, missingL0Card: true }
|
|
89
|
+
* when the partition dir exists but the l0-card is absent (partial onboard).
|
|
90
|
+
*/
|
|
91
|
+
function findWorkspaceState(cwd) {
|
|
92
|
+
if (!exists(AIKIT_WORKSPACES)) return null;
|
|
93
|
+
|
|
94
|
+
const partition = computePartitionKey(cwd);
|
|
95
|
+
const partitionDir = path.join(AIKIT_WORKSPACES, partition);
|
|
96
|
+
if (!exists(partitionDir)) return null;
|
|
97
|
+
|
|
98
|
+
const cardPath = path.join(partitionDir, 'l0-cards', 'workspace-core.md');
|
|
99
|
+
if (!exists(cardPath)) return { partition, missingL0Card: true };
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const stat = fs.statSync(cardPath);
|
|
103
|
+
return { partition, age: Date.now() - stat.mtimeMs };
|
|
104
|
+
} catch {
|
|
105
|
+
return { partition, missingL0Card: true };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check for active flows in workspace .flows/ directory.
|
|
111
|
+
* Flow runs are stored as .flows/<slug>/meta.json directories.
|
|
112
|
+
*/
|
|
113
|
+
function checkActiveFlows(cwd) {
|
|
114
|
+
const flowsDir = path.join(cwd, '.flows');
|
|
115
|
+
if (!exists(flowsDir)) return null;
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const entries = fs.readdirSync(flowsDir);
|
|
119
|
+
const activeFlows = [];
|
|
120
|
+
for (const entry of entries) {
|
|
121
|
+
const metaPath = path.join(flowsDir, entry, 'meta.json');
|
|
122
|
+
if (!exists(metaPath)) continue;
|
|
123
|
+
try {
|
|
124
|
+
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
|
|
125
|
+
if (meta?.status === 'active') {
|
|
126
|
+
activeFlows.push({ slug: entry, meta, mtime: fs.statSync(metaPath).mtimeMs });
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
// Skip unreadable meta files
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (activeFlows.length === 0) return null;
|
|
133
|
+
|
|
134
|
+
// Most recently modified active flow
|
|
135
|
+
const sorted = activeFlows.sort((a, b) => b.mtime - a.mtime);
|
|
136
|
+
const meta = sorted[0].meta;
|
|
137
|
+
return {
|
|
138
|
+
slug: sorted[0].slug,
|
|
139
|
+
status: meta.status || 'unknown',
|
|
140
|
+
// Flow scope may be in flow.scope, flow.roots, or flow.config.scope
|
|
141
|
+
scope: meta?.scope || meta?.roots || meta?.config?.scope || null,
|
|
142
|
+
};
|
|
143
|
+
} catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Signal workspace freshness state — silence when healthy. */
|
|
149
|
+
const handler = async (context) => {
|
|
150
|
+
const signals = [];
|
|
151
|
+
|
|
152
|
+
// 1. Scaffold check — look for manifest at deploy roots
|
|
153
|
+
const scaffold = checkScaffold(context.cwd);
|
|
154
|
+
if (!scaffold.deployed) {
|
|
155
|
+
signals.push('Scaffold not deployed. Run `aikit init` to set up workspace.');
|
|
156
|
+
} else if (!scaffold.valid) {
|
|
157
|
+
signals.push('Scaffold manifest corrupted. Run `aikit init --force` to repair.');
|
|
158
|
+
} else if (scaffold.stale) {
|
|
159
|
+
signals.push(
|
|
160
|
+
`Scaffold last deployed ${Math.round(scaffold.age / DAY_MS)} days ago. Consider \`aikit init\`.`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 2. Onboard / workspace state
|
|
165
|
+
const ws = findWorkspaceState(context.cwd);
|
|
166
|
+
if (!ws) {
|
|
167
|
+
signals.push(
|
|
168
|
+
'No onboard data found. Run `status()` or `onboard({ path: "." })` to initialize.',
|
|
169
|
+
);
|
|
170
|
+
} else if (ws.missingL0Card) {
|
|
171
|
+
signals.push(
|
|
172
|
+
'No workspace-core briefing found, though onboard directory exists. Run `onboard({ path: ".", mode: "generate" })` to generate it.',
|
|
173
|
+
);
|
|
174
|
+
} else if (ws.age > 30 * DAY_MS) {
|
|
175
|
+
signals.push(
|
|
176
|
+
`Onboard data stale (${Math.round(ws.age / DAY_MS)} days). Run \`onboard({ path: ".", update: true })\`.`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 3. Active flows
|
|
181
|
+
const flow = checkActiveFlows(context.cwd);
|
|
182
|
+
if (flow) {
|
|
183
|
+
signals.push(
|
|
184
|
+
`Active flow: ${flow.slug} (${flow.status}). Run \`flow({ action: 'status' })\` to check.`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Silence when healthy
|
|
189
|
+
if (signals.length === 0) return { decision: 'allow' };
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
additionalContext: `[Freshness]\n${signals.join('\n')}`,
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
createHook(handler, { event: 'SessionStart', category: 'signal', outputBudgetLines: 5 });
|