chainlesschain 0.45.11 → 0.45.19
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/src/assets/web-panel/assets/AppLayout-B00RARl2.js +1 -0
- package/src/assets/web-panel/assets/AppLayout-CFP4dGIJ.css +1 -0
- package/src/assets/web-panel/assets/{Chat-5f__rMCR.js → Chat-DXtvKoM0.js} +1 -1
- package/src/assets/web-panel/assets/{Cron-C4mrNC4c.js → Cron-BJ4ODHOy.js} +1 -1
- package/src/assets/web-panel/assets/Dashboard-3iIpp3zd.js +3 -0
- package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +1 -0
- package/src/assets/web-panel/assets/{Logs-CC_Zuh66.js → Logs-CSeKZEG_.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-B15GiN3u.js → McpTools-BYQAK11r.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-Dbd7oLOH.js → Memory-gkUAPyuZ.js} +2 -2
- package/src/assets/web-panel/assets/{Notes-CEkc49fY.js → Notes-bjNrQgAo.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CjyPHW00.js → Providers-Dbf57Tbv.js} +1 -1
- package/src/assets/web-panel/assets/{Services-XFzHMRRd.js → Services-CS0oMdxh.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-D8oxmB3U.js → Skills-B2fgruv8.js} +1 -1
- package/src/assets/web-panel/assets/Tasks-BJjN_YEm.css +1 -0
- package/src/assets/web-panel/assets/Tasks-qULws8pc.js +1 -0
- package/src/assets/web-panel/assets/{antd-ChLPLhSn.js → antd-CJSBocer.js} +1 -1
- package/src/assets/web-panel/assets/chat-DnH09sSR.js +1 -0
- package/src/assets/web-panel/assets/{index-DQ5xXK7O.js → index-CF2CqPYX.js} +2 -2
- package/src/assets/web-panel/assets/{markdown-DtbPhnFe.js → markdown-Bo5cVN4u.js} +1 -1
- package/src/assets/web-panel/assets/ws-DjelKkD6.js +1 -0
- package/src/assets/web-panel/index.html +2 -2
- package/src/commands/agent.js +7 -8
- package/src/commands/chat.js +9 -11
- package/src/commands/serve.js +11 -106
- package/src/commands/session.js +185 -18
- package/src/commands/ui.js +10 -151
- package/src/gateways/repl/agent-repl.js +1 -0
- package/src/gateways/repl/chat-repl.js +1 -0
- package/src/gateways/ui/web-ui-server.js +1 -0
- package/src/gateways/ws/action-protocol.js +83 -0
- package/src/gateways/ws/message-dispatcher.js +73 -0
- package/src/gateways/ws/session-protocol.js +396 -0
- package/src/gateways/ws/task-protocol.js +55 -0
- package/src/gateways/ws/worktree-protocol.js +315 -0
- package/src/gateways/ws/ws-server.js +4 -0
- package/src/gateways/ws/ws-session-gateway.js +1 -0
- package/src/harness/background-task-manager.js +506 -0
- package/src/harness/background-task-worker.js +48 -0
- package/src/harness/compression-telemetry.js +214 -0
- package/src/harness/feature-flags.js +157 -0
- package/src/harness/jsonl-session-store.js +452 -0
- package/src/harness/prompt-compressor.js +416 -0
- package/src/harness/worktree-isolator.js +845 -0
- package/src/lib/agent-core.js +246 -45
- package/src/lib/background-task-manager.js +1 -305
- package/src/lib/background-task-worker.js +1 -50
- package/src/lib/compression-telemetry.js +5 -0
- package/src/lib/feature-flags.js +7 -182
- package/src/lib/interaction-adapter.js +32 -6
- package/src/lib/jsonl-session-store.js +21 -237
- package/src/lib/prompt-compressor.js +10 -351
- package/src/lib/sub-agent-context.js +91 -0
- package/src/lib/worktree-isolator.js +13 -231
- package/src/lib/ws-agent-handler.js +1 -0
- package/src/lib/ws-server.js +155 -359
- package/src/lib/ws-session-manager.js +82 -1
- package/src/repl/agent-repl.js +114 -32
- package/src/runtime/agent-runtime.js +417 -0
- package/src/runtime/contracts/agent-turn.js +11 -0
- package/src/runtime/contracts/session-record.js +31 -0
- package/src/runtime/contracts/task-record.js +18 -0
- package/src/runtime/contracts/telemetry-record.js +23 -0
- package/src/runtime/contracts/worktree-record.js +14 -0
- package/src/runtime/index.js +13 -0
- package/src/runtime/policies/agent-policy.js +45 -0
- package/src/runtime/runtime-context.js +14 -0
- package/src/runtime/runtime-events.js +37 -0
- package/src/runtime/runtime-factory.js +50 -0
- package/src/tools/index.js +22 -0
- package/src/tools/legacy-agent-tools.js +171 -0
- package/src/tools/registry.js +141 -0
- package/src/tools/tool-context.js +28 -0
- package/src/tools/tool-permissions.js +28 -0
- package/src/tools/tool-telemetry.js +39 -0
- package/src/assets/web-panel/assets/AppLayout-19ZC8w11.js +0 -1
- package/src/assets/web-panel/assets/AppLayout-CjgO-ML6.css +0 -1
- package/src/assets/web-panel/assets/Dashboard-CRFnDUFh.css +0 -1
- package/src/assets/web-panel/assets/Dashboard-DsjXpZor.js +0 -3
- package/src/assets/web-panel/assets/chat-C_hu-qNs.js +0 -1
- package/src/assets/web-panel/assets/ws-DwluTqT5.js +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{g as Yc}from"./antd-
|
|
1
|
+
import{g as Yc}from"./antd-CJSBocer.js";function Ni(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}let He=Ni();function Mc(a){He=a}const Lc=/[&<>"']/,qc=new RegExp(Lc.source,"g"),xc=/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,Hc=new RegExp(xc.source,"g"),Vc={"&":"&","<":"<",">":">",'"':""","'":"'"},$i=a=>Vc[a];function Ne(a,e){if(e){if(Lc.test(a))return a.replace(qc,$i)}else if(xc.test(a))return a.replace(Hc,$i);return a}const zc=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig;function Wc(a){return a.replace(zc,(e,t)=>(t=t.toLowerCase(),t==="colon"?":":t.charAt(0)==="#"?t.charAt(1)==="x"?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""))}const $c=/(^|[^\[])\^/g;function Z(a,e){let t=typeof a=="string"?a:a.source;e=e||"";const n={replace:(r,i)=>{let o=typeof i=="string"?i:i.source;return o=o.replace($c,"$1"),t=t.replace(r,o),n},getRegex:()=>new RegExp(t,e)};return n}function Ki(a){try{a=encodeURI(a).replace(/%25/g,"%")}catch{return null}return a}const pt={exec:()=>null};function Qi(a,e){const t=a.replace(/\|/g,(i,o,s)=>{let l=!1,_=o;for(;--_>=0&&s[_]==="\\";)l=!l;return l?"|":" |"}),n=t.split(/ \|/);let r=0;if(n[0].trim()||n.shift(),n.length>0&&!n[n.length-1].trim()&&n.pop(),e)if(n.length>e)n.splice(e);else for(;n.length<e;)n.push("");for(;r<n.length;r++)n[r]=n[r].trim().replace(/\\\|/g,"|");return n}function yt(a,e,t){const n=a.length;if(n===0)return"";let r=0;for(;r<n&&a.charAt(n-r-1)===e;)r++;return a.slice(0,n-r)}function Kc(a,e){if(a.indexOf(e[1])===-1)return-1;let t=0;for(let n=0;n<a.length;n++)if(a[n]==="\\")n++;else if(a[n]===e[0])t++;else if(a[n]===e[1]&&(t--,t<0))return n;return-1}function Xi(a,e,t,n){const r=e.href,i=e.title?Ne(e.title):null,o=a[1].replace(/\\([\[\]])/g,"$1");if(a[0].charAt(0)!=="!"){n.state.inLink=!0;const s={type:"link",raw:t,href:r,title:i,text:o,tokens:n.inlineTokens(o)};return n.state.inLink=!1,s}return{type:"image",raw:t,href:r,title:i,text:Ne(o)}}function Qc(a,e){const t=a.match(/^(\s+)(?:```)/);if(t===null)return e;const n=t[1];return e.split(`
|
|
2
2
|
`).map(r=>{const i=r.match(/^\s+/);if(i===null)return r;const[o]=i;return o.length>=n.length?r.slice(n.length):r}).join(`
|
|
3
3
|
`)}class Lt{options;rules;lexer;constructor(e){this.options=e||He}space(e){const t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){const t=this.rules.block.code.exec(e);if(t){const n=t[0].replace(/^ {1,4}/gm,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?n:yt(n,`
|
|
4
4
|
`)}}}fences(e){const t=this.rules.block.fences.exec(e);if(t){const n=t[0],r=Qc(n,t[3]||"");return{type:"code",raw:n,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:r}}}heading(e){const t=this.rules.block.heading.exec(e);if(t){let n=t[2].trim();if(/#$/.test(n)){const r=yt(n,"#");(this.options.pedantic||!r||/ $/.test(r))&&(n=r.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:n,tokens:this.lexer.inline(n)}}}hr(e){const t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:t[0]}}blockquote(e){const t=this.rules.block.blockquote.exec(e);if(t){let n=t[0].replace(/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{V as H,f as E,c as z}from"./vendor-CN0Iv_qZ.js";let U=0;const p=()=>`wp-${++U}`,C=new Set;function d(e,n={},i={}){return{type:e,kind:i.kind||"server",sessionId:i.sessionId||n.sessionId||null,timestamp:i.timestamp||Date.now(),payload:n}}function B(e){switch(e?.type){case"task:notification":return d("task:notification",{task:e.task},{kind:"server"});case"session-created":return d("session:start",{sessionId:e.sessionId,sessionType:e.sessionType||null,record:e.record||{id:e.sessionId,type:e.sessionType||null,status:"created",history:[],messageCount:0}},{kind:"server",sessionId:e.sessionId});case"session-resumed":return d("session:resume",{sessionId:e.sessionId,history:e.history||[],historyCount:Array.isArray(e.history)?e.history.length:0,record:e.record||{id:e.sessionId,type:null,status:"resumed",history:e.history||[],messageCount:Array.isArray(e.history)?e.history.length:0}},{kind:"server",sessionId:e.sessionId});case"worktree-diff":return d("worktree:diff:ready",{requestId:e.id||null,record:e.record||{branch:e.branch||null,summary:e.summary||null,previewEntrypoints:[{type:"worktree-diff",branch:e.branch||null}]}},{kind:"server"});case"worktree-merged":return d("worktree:merge:completed",{requestId:e.id||null,record:e.record||{branch:e.branch||null,summary:e.summary||null,conflicts:e.conflicts||[],previewEntrypoints:e.previewEntrypoints||[]}},{kind:"server"});case"compression-stats":return d("compression:summary",{requestId:e.id||null,summary:e.summary||{}},{kind:"server"});default:return null}}function N(e,n=null){C.forEach(i=>i(e,n||e))}function F(e){if(!e)return null;const n=e.record||{id:e.id||null,type:e.type||null,provider:e.provider||null,model:e.model||null,projectRoot:e.projectRoot||null,messageCount:e.messageCount??0,history:e.history||[],status:e.status||null};return{...e,id:e.id||n.id,type:e.type||n.type,provider:e.provider||n.provider,model:e.model||n.model,projectRoot:e.projectRoot||n.projectRoot,messageCount:e.messageCount??n.messageCount??0,status:e.status||n.status||null,record:n}}const Q=H("ws",()=>{const e=E(null),n=E("disconnected"),i=E(null),c=new Map,a=new Map;let b=null,w=1e3;const y=window.__CC_CONFIG__||{},I=z(()=>`ws://${y.wsHost||"127.0.0.1"}:${y.wsPort||18800}`);function h(){if(e.value?.readyState!==WebSocket.OPEN){n.value="connecting",i.value=null;try{const t=new WebSocket(I.value);e.value=t,t.onopen=()=>{w=1e3,y.wsToken?l({type:"auth",id:p(),token:y.wsToken}).then(()=>{n.value="connected"}).catch(()=>{n.value="connected"}):n.value="connected"},t.onmessage=o=>{let r;try{r=JSON.parse(o.data)}catch{return}T(r)},t.onerror=()=>{i.value="WebSocket error",n.value="error"},t.onclose=()=>{n.value="disconnected",e.value=null,c.forEach(({reject:o})=>o(new Error("WebSocket closed"))),c.clear(),b=setTimeout(()=>{w=Math.min(w*2,3e4),h()},w)}}catch(t){n.value="error",i.value=t.message}}}function O(){clearTimeout(b),e.value?.close(),e.value=null,n.value="disconnected"}function T(t){const{type:o,id:r}=t;let u=!1;if(r&&c.has(r)){const{resolve:m,reject:k,timeout:D}=c.get(r);clearTimeout(D),c.delete(r),u=!0,o==="error"?k(new Error(t.message||"Unknown error")):m(t)}const s=t.sessionId;s&&a.has(s)&&a.get(s).forEach(k=>k(t)),S.forEach(m=>m(t));const v=B(t);return v&&N(v,t),u}const S=new Set;function W(t){return S.add(t),()=>S.delete(t)}function P(t){return C.add(t),()=>C.delete(t)}function J(t,o){return a.has(t)||a.set(t,new Set),a.get(t).add(o),()=>{a.get(t)?.delete(o),a.get(t)?.size===0&&a.delete(t)}}function l(t,o=15e3){return new Promise((r,u)=>{if(e.value?.readyState!==WebSocket.OPEN){u(new Error("WebSocket not connected"));return}const s=t.id||p(),v=setTimeout(()=>{c.delete(s),u(new Error("Request timeout"))},o);c.set(s,{resolve:r,reject:u,timeout:v}),e.value.send(JSON.stringify({...t,id:s}))})}function f(t=8e3){return n.value==="connected"?Promise.resolve():new Promise((o,r)=>{const u=Date.now()+t,s=()=>{if(n.value==="connected")return o();if(n.value==="error"||Date.now()>=u)return r(new Error(`WS not ready: ${n.value}`));setTimeout(s,150)};n.value==="disconnected"&&h(),s()})}async function R(t,o=3e4){await f(8e3);const r=await l({type:"execute",command:t},o),u=r.output??r.stdout??"",s=r.stderr??"";return{output:u||s,exitCode:r.exitCode??0}}async function j(t,o=3e4){const{output:r,exitCode:u}=await R(t,o);if(u!==0)throw new Error(`Command failed: ${r}`);try{return JSON.parse(r.trim())}catch{const s=r.match(/\{[\s\S]*\}|\[[\s\S]*\]/);if(s)return JSON.parse(s[0]);throw new Error(`Invalid JSON output: ${r.slice(0,200)}`)}}async function M(t="chat",o=null){await f(8e3);const r=p();return(await l({type:"session-create",id:r,sessionType:t,projectRoot:o||y.projectRoot||null})).sessionId}function _(t,o){e.value?.readyState===WebSocket.OPEN&&e.value.send(JSON.stringify({type:"session-message",id:p(),sessionId:t,content:o}))}function $(t,o,r){e.value?.readyState===WebSocket.OPEN&&e.value.send(JSON.stringify({type:"session-answer",id:p(),sessionId:t,requestId:o,answer:r}))}async function x(){return await f(8e3),((await l({type:"session-list"},1e4)).sessions||[]).map(F).filter(Boolean)}async function q(t){try{await l({type:"session-close",sessionId:t},5e3),N(d("session:end",{sessionId:t},{kind:"server",sessionId:t}),{type:"result",sessionId:t,success:!0})}catch{}}async function A(t){return await f(8e3),await l({type:"session-resume",sessionId:t},1e4)}return{status:n,error:i,wsUrl:I,connect:h,disconnect:O,waitConnected:f,onMessage:W,onRuntimeEvent:P,onSession:J,sendRaw:l,execute:R,executeJson:j,createSession:M,resumeSession:A,sendSessionMessage:_,answerQuestion:$,listSessions:x,closeSession:q}});export{Q as u};
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
// Injected by web-ui-server.js at serve time
|
|
9
9
|
window.__CC_CONFIG__ = __CC_CONFIG_PLACEHOLDER__;
|
|
10
10
|
</script>
|
|
11
|
-
<script type="module" crossorigin src="./assets/index-
|
|
11
|
+
<script type="module" crossorigin src="./assets/index-CF2CqPYX.js"></script>
|
|
12
12
|
<link rel="modulepreload" crossorigin href="./assets/vendor-CN0Iv_qZ.js">
|
|
13
|
-
<link rel="modulepreload" crossorigin href="./assets/antd-
|
|
13
|
+
<link rel="modulepreload" crossorigin href="./assets/antd-CJSBocer.js">
|
|
14
14
|
<link rel="stylesheet" crossorigin href="./assets/index-CyGyEIVX.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
package/src/commands/agent.js
CHANGED
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
* AI reads files, writes code, runs commands, and explains what it's doing.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import { loadConfig } from "../lib/config-manager.js";
|
|
9
|
+
import { createAgentRuntimeFactory } from "../runtime/runtime-factory.js";
|
|
11
10
|
|
|
12
11
|
export function registerAgentCommand(program) {
|
|
13
12
|
program
|
|
@@ -25,13 +24,13 @@ export function registerAgentCommand(program) {
|
|
|
25
24
|
.option("--api-key <key>", "API key")
|
|
26
25
|
.option("--session <id>", "Resume a previous agent session")
|
|
27
26
|
.action(async (options) => {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
apiKey: options.apiKey || config.llm?.apiKey,
|
|
27
|
+
const runtime = createAgentRuntimeFactory().createAgentRuntime({
|
|
28
|
+
model: options.model,
|
|
29
|
+
provider: options.provider,
|
|
30
|
+
baseUrl: options.baseUrl,
|
|
31
|
+
apiKey: options.apiKey,
|
|
34
32
|
sessionId: options.session,
|
|
35
33
|
});
|
|
34
|
+
await runtime.startAgentSession();
|
|
36
35
|
});
|
|
37
36
|
}
|
package/src/commands/chat.js
CHANGED
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
* chainlesschain chat [--model] [--provider] [--agent]
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import { startAgentRepl } from "../repl/agent-repl.js";
|
|
8
|
-
import { loadConfig } from "../lib/config-manager.js";
|
|
6
|
+
import { createAgentRuntimeFactory } from "../runtime/runtime-factory.js";
|
|
9
7
|
|
|
10
8
|
export function registerChatCommand(program) {
|
|
11
9
|
program
|
|
@@ -24,19 +22,19 @@ export function registerChatCommand(program) {
|
|
|
24
22
|
)
|
|
25
23
|
.option("--session <id>", "Resume a previous session (agent mode)")
|
|
26
24
|
.action(async (options) => {
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
model: options.model
|
|
30
|
-
provider: options.provider
|
|
31
|
-
baseUrl: options.baseUrl
|
|
32
|
-
apiKey: options.apiKey
|
|
25
|
+
const factory = createAgentRuntimeFactory();
|
|
26
|
+
const runtimeOptions = {
|
|
27
|
+
model: options.model,
|
|
28
|
+
provider: options.provider,
|
|
29
|
+
baseUrl: options.baseUrl,
|
|
30
|
+
apiKey: options.apiKey,
|
|
33
31
|
sessionId: options.session,
|
|
34
32
|
};
|
|
35
33
|
|
|
36
34
|
if (options.agent) {
|
|
37
|
-
await
|
|
35
|
+
await factory.createAgentRuntime(runtimeOptions).startAgentSession();
|
|
38
36
|
} else {
|
|
39
|
-
await
|
|
37
|
+
await factory.createChatRuntime(runtimeOptions).startChatSession();
|
|
40
38
|
}
|
|
41
39
|
});
|
|
42
40
|
}
|
package/src/commands/serve.js
CHANGED
|
@@ -3,11 +3,8 @@
|
|
|
3
3
|
* chainlesschain serve [--port] [--host] [--token] [--max-connections] [--timeout] [--allow-remote] [--project]
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import chalk from "chalk";
|
|
7
6
|
import { logger } from "../lib/logger.js";
|
|
8
|
-
import {
|
|
9
|
-
import { WSSessionManager } from "../lib/ws-session-manager.js";
|
|
10
|
-
import { bootstrap } from "../runtime/bootstrap.js";
|
|
7
|
+
import { createAgentRuntimeFactory } from "../runtime/runtime-factory.js";
|
|
11
8
|
|
|
12
9
|
export function registerServeCommand(program) {
|
|
13
10
|
program
|
|
@@ -31,109 +28,17 @@ export function registerServeCommand(program) {
|
|
|
31
28
|
)
|
|
32
29
|
.option("--project <path>", "Default project root for sessions")
|
|
33
30
|
.action(async (opts) => {
|
|
34
|
-
const port = parseInt(opts.port, 10);
|
|
35
|
-
const maxConnections = parseInt(opts.maxConnections, 10);
|
|
36
|
-
const timeout = parseInt(opts.timeout, 10);
|
|
37
|
-
let host = opts.host;
|
|
38
|
-
|
|
39
|
-
// Validation
|
|
40
|
-
if (isNaN(port) || port < 1 || port > 65535) {
|
|
41
|
-
logger.error("Invalid port number. Must be between 1 and 65535.");
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (opts.allowRemote) {
|
|
46
|
-
if (!opts.token) {
|
|
47
|
-
logger.error("--allow-remote requires --token for security.");
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
|
-
host = "0.0.0.0";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Bootstrap headless runtime for DB access
|
|
54
|
-
let db = null;
|
|
55
|
-
try {
|
|
56
|
-
const ctx = await bootstrap({ skipDb: false });
|
|
57
|
-
db = ctx.db?.getDb?.() || null;
|
|
58
|
-
} catch (_err) {
|
|
59
|
-
logger.log(
|
|
60
|
-
chalk.yellow(
|
|
61
|
-
" Warning: Database not available, sessions will be in-memory only",
|
|
62
|
-
),
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Create session manager
|
|
67
|
-
const sessionManager = new WSSessionManager({
|
|
68
|
-
db,
|
|
69
|
-
defaultProjectRoot: opts.project || process.cwd(),
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
const server = new ChainlessChainWSServer({
|
|
73
|
-
port,
|
|
74
|
-
host,
|
|
75
|
-
token: opts.token || null,
|
|
76
|
-
maxConnections,
|
|
77
|
-
timeout,
|
|
78
|
-
sessionManager,
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// Event logging
|
|
82
|
-
server.on("connection", ({ clientId, ip }) => {
|
|
83
|
-
logger.log(chalk.green(` + Client connected: ${clientId} (${ip})`));
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
server.on("disconnection", ({ clientId, reason }) => {
|
|
87
|
-
const extra = reason ? ` (${reason})` : "";
|
|
88
|
-
logger.log(
|
|
89
|
-
chalk.yellow(` - Client disconnected: ${clientId}${extra}`),
|
|
90
|
-
);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
server.on("command:start", ({ id, command }) => {
|
|
94
|
-
logger.log(chalk.cyan(` > [${id}] ${command}`));
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
server.on("command:end", ({ id, exitCode }) => {
|
|
98
|
-
const color = exitCode === 0 ? chalk.green : chalk.red;
|
|
99
|
-
logger.log(color(` < [${id}] exit ${exitCode}`));
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
server.on("session:create", ({ sessionId, type }) => {
|
|
103
|
-
logger.log(chalk.green(` + Session created: ${sessionId} (${type})`));
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
server.on("session:close", ({ sessionId }) => {
|
|
107
|
-
logger.log(chalk.yellow(` - Session closed: ${sessionId}`));
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// Graceful shutdown
|
|
111
|
-
const shutdown = async () => {
|
|
112
|
-
logger.log("\n" + chalk.yellow("Shutting down WebSocket server..."));
|
|
113
|
-
await server.stop();
|
|
114
|
-
process.exit(0);
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
process.on("SIGINT", shutdown);
|
|
118
|
-
process.on("SIGTERM", shutdown);
|
|
119
|
-
|
|
120
31
|
try {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
logger.log(` Project: ${opts.project || process.cwd()}`);
|
|
132
|
-
logger.log(` Max conn: ${maxConnections}`);
|
|
133
|
-
logger.log(` Timeout: ${timeout}ms`);
|
|
134
|
-
logger.log("");
|
|
135
|
-
logger.log(chalk.dim(" Press Ctrl+C to stop"));
|
|
136
|
-
logger.log("");
|
|
32
|
+
const runtime = createAgentRuntimeFactory().createServerRuntime({
|
|
33
|
+
port: parseInt(opts.port, 10),
|
|
34
|
+
host: opts.host,
|
|
35
|
+
token: opts.token,
|
|
36
|
+
maxConnections: parseInt(opts.maxConnections, 10),
|
|
37
|
+
timeout: parseInt(opts.timeout, 10),
|
|
38
|
+
allowRemote: opts.allowRemote,
|
|
39
|
+
project: opts.project,
|
|
40
|
+
});
|
|
41
|
+
await runtime.startServer();
|
|
137
42
|
} catch (err) {
|
|
138
43
|
logger.error(`Failed to start server: ${err.message}`);
|
|
139
44
|
process.exit(1);
|
package/src/commands/session.js
CHANGED
|
@@ -13,6 +13,17 @@ import {
|
|
|
13
13
|
deleteSession,
|
|
14
14
|
exportSessionMarkdown,
|
|
15
15
|
} from "../lib/session-manager.js";
|
|
16
|
+
import {
|
|
17
|
+
listJsonlSessions,
|
|
18
|
+
rebuildMessages,
|
|
19
|
+
sessionExists,
|
|
20
|
+
readEvents,
|
|
21
|
+
migrateLegacySessions,
|
|
22
|
+
migrateLegacySessionsBatch,
|
|
23
|
+
validateJsonlSession,
|
|
24
|
+
validateAllJsonlSessions,
|
|
25
|
+
} from "../lib/jsonl-session-store.js";
|
|
26
|
+
import { feature } from "../lib/feature-flags.js";
|
|
16
27
|
|
|
17
28
|
export function registerSessionCommand(program) {
|
|
18
29
|
const session = program
|
|
@@ -28,14 +39,39 @@ export function registerSessionCommand(program) {
|
|
|
28
39
|
.action(async (options) => {
|
|
29
40
|
try {
|
|
30
41
|
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
const limit = Math.max(1, parseInt(options.limit) || 20);
|
|
43
|
+
let sessions = [];
|
|
44
|
+
|
|
45
|
+
// Merge DB sessions + JSONL sessions
|
|
46
|
+
if (ctx.db) {
|
|
47
|
+
const db = ctx.db.getDatabase();
|
|
48
|
+
sessions.push(
|
|
49
|
+
...listSessions(db, { limit }).map((s) => ({
|
|
50
|
+
...s,
|
|
51
|
+
_store: "db",
|
|
52
|
+
})),
|
|
53
|
+
);
|
|
34
54
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
55
|
+
|
|
56
|
+
if (feature("JSONL_SESSION")) {
|
|
57
|
+
sessions.push(
|
|
58
|
+
...listJsonlSessions({ limit }).map((s) => ({
|
|
59
|
+
...s,
|
|
60
|
+
_store: "jsonl",
|
|
61
|
+
})),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Deduplicate by id (JSONL takes precedence), sort by updated_at
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
sessions = sessions
|
|
68
|
+
.sort((a, b) => (b.updated_at > a.updated_at ? 1 : -1))
|
|
69
|
+
.filter((s) => {
|
|
70
|
+
if (seen.has(s.id)) return false;
|
|
71
|
+
seen.add(s.id);
|
|
72
|
+
return true;
|
|
73
|
+
})
|
|
74
|
+
.slice(0, limit);
|
|
39
75
|
|
|
40
76
|
if (options.json) {
|
|
41
77
|
console.log(JSON.stringify(sessions, null, 2));
|
|
@@ -46,8 +82,10 @@ export function registerSessionCommand(program) {
|
|
|
46
82
|
} else {
|
|
47
83
|
logger.log(chalk.bold(`Sessions (${sessions.length}):\n`));
|
|
48
84
|
for (const s of sessions) {
|
|
85
|
+
const storeTag =
|
|
86
|
+
s._store === "jsonl" ? chalk.yellow("[JSONL]") : "";
|
|
49
87
|
logger.log(
|
|
50
|
-
` ${chalk.gray(s.id.slice(0, 16))} ${chalk.white(s.title)} ${chalk.cyan(s.message_count + " msgs")} ${chalk.gray(s.updated_at)}`,
|
|
88
|
+
` ${chalk.gray(s.id.slice(0, 16))} ${chalk.white(s.title)} ${chalk.cyan(s.message_count + " msgs")} ${chalk.gray(s.updated_at)} ${storeTag}`,
|
|
51
89
|
);
|
|
52
90
|
if (s.summary) {
|
|
53
91
|
logger.log(` ${chalk.gray(s.summary.substring(0, 100))}`);
|
|
@@ -72,12 +110,29 @@ export function registerSessionCommand(program) {
|
|
|
72
110
|
.action(async (id, options) => {
|
|
73
111
|
try {
|
|
74
112
|
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
113
|
+
let sess = null;
|
|
114
|
+
|
|
115
|
+
// Try JSONL first if enabled
|
|
116
|
+
if (feature("JSONL_SESSION") && sessionExists(id)) {
|
|
117
|
+
const events = readEvents(id);
|
|
118
|
+
const startEvent = events.find((e) => e.type === "session_start");
|
|
119
|
+
const msgs = rebuildMessages(id);
|
|
120
|
+
sess = {
|
|
121
|
+
id,
|
|
122
|
+
title: startEvent?.data?.title || "Untitled",
|
|
123
|
+
provider: startEvent?.data?.provider || "",
|
|
124
|
+
model: startEvent?.data?.model || "",
|
|
125
|
+
message_count: msgs.length,
|
|
126
|
+
messages: msgs,
|
|
127
|
+
_store: "jsonl",
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Fallback to DB
|
|
132
|
+
if (!sess && ctx.db) {
|
|
133
|
+
const db = ctx.db.getDatabase();
|
|
134
|
+
sess = getSession(db, id);
|
|
78
135
|
}
|
|
79
|
-
const db = ctx.db.getDatabase();
|
|
80
|
-
const sess = getSession(db, id);
|
|
81
136
|
|
|
82
137
|
if (!sess) {
|
|
83
138
|
logger.error(`Session not found: ${id}`);
|
|
@@ -127,12 +182,27 @@ export function registerSessionCommand(program) {
|
|
|
127
182
|
.action(async (id, options) => {
|
|
128
183
|
try {
|
|
129
184
|
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
185
|
+
let sess = null;
|
|
186
|
+
|
|
187
|
+
// Try JSONL first
|
|
188
|
+
if (feature("JSONL_SESSION") && sessionExists(id)) {
|
|
189
|
+
const events = readEvents(id);
|
|
190
|
+
const startEvent = events.find((e) => e.type === "session_start");
|
|
191
|
+
sess = {
|
|
192
|
+
id,
|
|
193
|
+
title: startEvent?.data?.title || "Untitled",
|
|
194
|
+
provider: startEvent?.data?.provider || "",
|
|
195
|
+
model: startEvent?.data?.model || "",
|
|
196
|
+
messages: rebuildMessages(id),
|
|
197
|
+
};
|
|
198
|
+
sess.message_count = sess.messages.length;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Fallback to DB
|
|
202
|
+
if (!sess && ctx.db) {
|
|
203
|
+
const db = ctx.db.getDatabase();
|
|
204
|
+
sess = getSession(db, id);
|
|
133
205
|
}
|
|
134
|
-
const db = ctx.db.getDatabase();
|
|
135
|
-
const sess = getSession(db, id);
|
|
136
206
|
|
|
137
207
|
if (!sess) {
|
|
138
208
|
logger.error(`Session not found: ${id}`);
|
|
@@ -235,4 +305,101 @@ export function registerSessionCommand(program) {
|
|
|
235
305
|
process.exit(1);
|
|
236
306
|
}
|
|
237
307
|
});
|
|
308
|
+
|
|
309
|
+
session
|
|
310
|
+
.command("migrate")
|
|
311
|
+
.description("Migrate legacy JSON session files to JSONL")
|
|
312
|
+
.argument("[source]", "Directory containing legacy .json session files")
|
|
313
|
+
.option("--dry-run", "Show what would migrate without writing files")
|
|
314
|
+
.option("--force", "Overwrite existing JSONL sessions")
|
|
315
|
+
.option("--no-archive", "Do not keep .migrated.json backups")
|
|
316
|
+
.option("--sample-size <n>", "Validate N migrated sessions after migration", "3")
|
|
317
|
+
.option("--retry-failures", "Retry failed migrations once")
|
|
318
|
+
.option("--json", "Output as JSON")
|
|
319
|
+
.action(async (source, options) => {
|
|
320
|
+
try {
|
|
321
|
+
const report = migrateLegacySessionsBatch(source, {
|
|
322
|
+
dryRun: options.dryRun,
|
|
323
|
+
force: options.force,
|
|
324
|
+
archive: options.archive,
|
|
325
|
+
sampleSize: parseInt(options.sampleSize, 10) || 3,
|
|
326
|
+
retryFailures: options.retryFailures,
|
|
327
|
+
});
|
|
328
|
+
const results = report.results || migrateLegacySessions(source, options);
|
|
329
|
+
|
|
330
|
+
if (options.json) {
|
|
331
|
+
console.log(JSON.stringify(report, null, 2));
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (results.length === 0) {
|
|
336
|
+
logger.info("No legacy JSON session files found.");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
for (const result of results) {
|
|
341
|
+
if (result.skipped) {
|
|
342
|
+
logger.log(
|
|
343
|
+
`${chalk.yellow("skip")} ${result.file} -> ${result.sessionId} (${result.reason})`,
|
|
344
|
+
);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
logger.log(
|
|
348
|
+
`${chalk.green(options.dryRun ? "plan" : "migrated")} ${result.file} -> ${result.sessionId} (${result.messageCount} messages)`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
logger.log(
|
|
353
|
+
chalk.gray(
|
|
354
|
+
`summary: scanned ${report.summary.scanned}, migrated ${report.summary.migrated}, skipped ${report.summary.skipped}, failed ${report.summary.failed}, retries ${report.summary.retries}`,
|
|
355
|
+
),
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
if (report.sampledValidation?.length) {
|
|
359
|
+
for (const item of report.sampledValidation) {
|
|
360
|
+
const label = item.valid && item.matchesExpectedMessages
|
|
361
|
+
? chalk.green("sample-ok")
|
|
362
|
+
: chalk.red("sample-fail");
|
|
363
|
+
logger.log(
|
|
364
|
+
`${label} ${item.sessionId} (${item.messageCount}/${item.expectedMessageCount} messages)`,
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
} catch (err) {
|
|
369
|
+
logger.error(`Failed: ${err.message}`);
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
session
|
|
375
|
+
.command("validate")
|
|
376
|
+
.description("Validate JSONL session files")
|
|
377
|
+
.argument("[id]", "Session ID to validate")
|
|
378
|
+
.option("--json", "Output as JSON")
|
|
379
|
+
.action(async (id, options) => {
|
|
380
|
+
try {
|
|
381
|
+
const result = id
|
|
382
|
+
? validateJsonlSession(id)
|
|
383
|
+
: validateAllJsonlSessions();
|
|
384
|
+
|
|
385
|
+
if (options.json) {
|
|
386
|
+
console.log(JSON.stringify(result, null, 2));
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const results = Array.isArray(result) ? result : [result];
|
|
391
|
+
for (const item of results) {
|
|
392
|
+
const label = item.valid ? chalk.green("valid") : chalk.red("invalid");
|
|
393
|
+
logger.log(
|
|
394
|
+
`${label} ${item.sessionId} (${item.eventCount} events, ${item.messageCount || 0} messages, malformed: ${item.malformedLines})`,
|
|
395
|
+
);
|
|
396
|
+
if (!item.valid && item.reason) {
|
|
397
|
+
logger.log(` ${chalk.gray(item.reason)}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
} catch (err) {
|
|
401
|
+
logger.error(`Failed: ${err.message}`);
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
238
405
|
}
|
package/src/commands/ui.js
CHANGED
|
@@ -1,39 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ui command — start a local web management UI
|
|
3
|
-
* chainlesschain ui [--port] [--ws-port] [--host] [--no-open] [--token]
|
|
4
|
-
*
|
|
5
|
-
* Project mode (run from a dir with .chainlesschain/): project-scoped chat UI
|
|
6
|
-
* Global mode (run from any other dir): global management panel
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { execSync } from "child_process";
|
|
10
|
-
import path from "path";
|
|
11
|
-
import chalk from "chalk";
|
|
12
1
|
import { logger } from "../lib/logger.js";
|
|
13
|
-
import {
|
|
14
|
-
import { WSSessionManager } from "../lib/ws-session-manager.js";
|
|
15
|
-
import { createWebUIServer } from "../lib/web-ui-server.js";
|
|
16
|
-
import { bootstrap } from "../runtime/bootstrap.js";
|
|
17
|
-
import { findProjectRoot, loadProjectConfig } from "../lib/project-detector.js";
|
|
18
|
-
import { loadConfig } from "../lib/config-manager.js";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Open a URL in the system default browser (cross-platform).
|
|
22
|
-
*/
|
|
23
|
-
function openBrowser(url) {
|
|
24
|
-
try {
|
|
25
|
-
const platform = process.platform;
|
|
26
|
-
if (platform === "win32") {
|
|
27
|
-
execSync(`start "" "${url}"`, { stdio: "ignore" });
|
|
28
|
-
} else if (platform === "darwin") {
|
|
29
|
-
execSync(`open "${url}"`, { stdio: "ignore" });
|
|
30
|
-
} else {
|
|
31
|
-
execSync(`xdg-open "${url}"`, { stdio: "ignore" });
|
|
32
|
-
}
|
|
33
|
-
} catch (_err) {
|
|
34
|
-
// Non-critical — user can open manually
|
|
35
|
-
}
|
|
36
|
-
}
|
|
2
|
+
import { createAgentRuntimeFactory } from "../runtime/runtime-factory.js";
|
|
37
3
|
|
|
38
4
|
export function registerUiCommand(program) {
|
|
39
5
|
program
|
|
@@ -52,126 +18,19 @@ export function registerUiCommand(program) {
|
|
|
52
18
|
"Path to built web-panel dist/ directory (auto-detected by default)",
|
|
53
19
|
)
|
|
54
20
|
.action(async (opts) => {
|
|
55
|
-
const httpPort = parseInt(opts.port, 10);
|
|
56
|
-
const wsPort = parseInt(opts.wsPort, 10);
|
|
57
|
-
const host = opts.host;
|
|
58
|
-
|
|
59
|
-
if (isNaN(httpPort) || httpPort < 1 || httpPort > 65535) {
|
|
60
|
-
logger.error("Invalid --port. Must be between 1 and 65535.");
|
|
61
|
-
process.exit(1);
|
|
62
|
-
}
|
|
63
|
-
if (isNaN(wsPort) || wsPort < 1 || wsPort > 65535) {
|
|
64
|
-
logger.error("Invalid --ws-port. Must be between 1 and 65535.");
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// ── Detect project context ────────────────────────────────────────────
|
|
69
|
-
const projectRoot = findProjectRoot(process.cwd());
|
|
70
|
-
const projectConfig = projectRoot ? loadProjectConfig(projectRoot) : null;
|
|
71
|
-
const projectName =
|
|
72
|
-
projectConfig?.name ||
|
|
73
|
-
(projectRoot ? path.basename(projectRoot) : null);
|
|
74
|
-
const mode = projectRoot ? "project" : "global";
|
|
75
|
-
|
|
76
|
-
// ── Bootstrap headless runtime ────────────────────────────────────────
|
|
77
|
-
let db = null;
|
|
78
|
-
try {
|
|
79
|
-
const ctx = await bootstrap({ skipDb: false });
|
|
80
|
-
db = ctx.db?.getDb?.() || null;
|
|
81
|
-
} catch (_err) {
|
|
82
|
-
logger.log(
|
|
83
|
-
chalk.yellow(
|
|
84
|
-
" Warning: Database not available, sessions will be in-memory only",
|
|
85
|
-
),
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// ── Start WebSocket server ────────────────────────────────────────────
|
|
90
|
-
const appConfig = loadConfig();
|
|
91
|
-
const sessionManager = new WSSessionManager({
|
|
92
|
-
db,
|
|
93
|
-
defaultProjectRoot: projectRoot || process.cwd(),
|
|
94
|
-
config: appConfig,
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const wsServer = new ChainlessChainWSServer({
|
|
98
|
-
port: wsPort,
|
|
99
|
-
host,
|
|
100
|
-
token: opts.token || null,
|
|
101
|
-
maxConnections: 20,
|
|
102
|
-
timeout: 60000,
|
|
103
|
-
sessionManager,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
await wsServer.start();
|
|
108
|
-
} catch (err) {
|
|
109
|
-
logger.error(`Failed to start WebSocket server: ${err.message}`);
|
|
110
|
-
process.exit(1);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// ── Start HTTP server ─────────────────────────────────────────────────
|
|
114
|
-
const httpServer = createWebUIServer({
|
|
115
|
-
wsPort,
|
|
116
|
-
wsToken: opts.token || null,
|
|
117
|
-
wsHost: host === "0.0.0.0" ? "127.0.0.1" : host,
|
|
118
|
-
projectRoot,
|
|
119
|
-
projectName,
|
|
120
|
-
mode,
|
|
121
|
-
staticDir: opts.webPanelDir || null,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
21
|
try {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
22
|
+
const runtime = createAgentRuntimeFactory().createUiRuntime({
|
|
23
|
+
port: parseInt(opts.port, 10),
|
|
24
|
+
wsPort: parseInt(opts.wsPort, 10),
|
|
25
|
+
host: opts.host,
|
|
26
|
+
open: opts.open,
|
|
27
|
+
token: opts.token || null,
|
|
28
|
+
webPanelDir: opts.webPanelDir || null,
|
|
128
29
|
});
|
|
30
|
+
await runtime.startUiServer();
|
|
129
31
|
} catch (err) {
|
|
130
|
-
logger.error(`Failed to start
|
|
32
|
+
logger.error(`Failed to start UI server: ${err.message}`);
|
|
131
33
|
process.exit(1);
|
|
132
34
|
}
|
|
133
|
-
|
|
134
|
-
// ── Print startup info ────────────────────────────────────────────────
|
|
135
|
-
const uiUrl = `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${httpPort}`;
|
|
136
|
-
|
|
137
|
-
logger.log("");
|
|
138
|
-
logger.log(chalk.bold(" ChainlessChain 管理面板"));
|
|
139
|
-
logger.log("");
|
|
140
|
-
if (mode === "project") {
|
|
141
|
-
logger.log(
|
|
142
|
-
` Mode: ${chalk.cyan("project")} ${chalk.dim(projectRoot)}`,
|
|
143
|
-
);
|
|
144
|
-
if (projectName) {
|
|
145
|
-
logger.log(` Project: ${chalk.green(projectName)}`);
|
|
146
|
-
}
|
|
147
|
-
} else {
|
|
148
|
-
logger.log(` Mode: ${chalk.cyan("global")}`);
|
|
149
|
-
}
|
|
150
|
-
logger.log(` UI: ${chalk.cyan(uiUrl)}`);
|
|
151
|
-
logger.log(` WS: ${chalk.dim(`ws://${host}:${wsPort}`)}`);
|
|
152
|
-
logger.log(
|
|
153
|
-
` Auth: ${opts.token ? chalk.green("enabled") : chalk.yellow("disabled")}`,
|
|
154
|
-
);
|
|
155
|
-
logger.log("");
|
|
156
|
-
logger.log(chalk.dim(" Press Ctrl+C to stop"));
|
|
157
|
-
logger.log("");
|
|
158
|
-
|
|
159
|
-
// ── Open browser ──────────────────────────────────────────────────────
|
|
160
|
-
if (opts.open !== false) {
|
|
161
|
-
openBrowser(uiUrl);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ── Graceful shutdown ─────────────────────────────────────────────────
|
|
165
|
-
const shutdown = async () => {
|
|
166
|
-
logger.log("\n" + chalk.yellow("Shutting down UI server..."));
|
|
167
|
-
await Promise.all([
|
|
168
|
-
new Promise((resolve) => httpServer.close(resolve)),
|
|
169
|
-
wsServer.stop(),
|
|
170
|
-
]);
|
|
171
|
-
process.exit(0);
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
process.on("SIGINT", shutdown);
|
|
175
|
-
process.on("SIGTERM", shutdown);
|
|
176
35
|
});
|
|
177
36
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { startAgentRepl } from "../../repl/agent-repl.js";
|