@weavecode/weave 0.1.1 → 0.1.2
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/node_modules/@weavecode/cli/dist/cli.js +4 -4
- package/node_modules/@weavecode/cli/dist/commands/chpass.js +1 -1
- package/node_modules/@weavecode/cli/dist/commands/daemon-auth.js +1 -1
- package/node_modules/@weavecode/cli/dist/commands/daemon.d.ts +7 -0
- package/node_modules/@weavecode/cli/dist/commands/daemon.js +2 -2
- package/node_modules/@weavecode/cli/dist/commands/dashboard.js +2 -2
- package/node_modules/@weavecode/cli/dist/commands/doctor.js +1 -1
- package/node_modules/@weavecode/cli/dist/commands/init.js +1 -1
- package/node_modules/@weavecode/cli/dist/commands/scan.js +1 -1
- package/node_modules/@weavecode/cli/dist/commands/status.js +2 -2
- package/node_modules/@weavecode/cli/dist/commands/uninstall.js +1 -1
- package/node_modules/@weavecode/cli/dist/i18n/dict/en.js +1 -9
- package/node_modules/@weavecode/cli/dist/i18n/dict/zh.d.ts +0 -1
- package/node_modules/@weavecode/cli/dist/i18n/dict/zh.js +2 -10
- package/node_modules/@weavecode/cli/dist/i18n/index.js +1 -1
- package/node_modules/@weavecode/cli/dist/i18n/t.js +1 -1
- package/node_modules/@weavecode/cli/dist/init/claudemd-gen.d.ts +2 -3
- package/node_modules/@weavecode/cli/dist/init/claudemd-gen.js +4 -4
- package/node_modules/@weavecode/cli/dist/init/executor.js +3 -3
- package/node_modules/@weavecode/cli/dist/init/git-commit.js +2 -2
- package/node_modules/@weavecode/cli/dist/init/harnesses/claude.js +7 -7
- package/node_modules/@weavecode/cli/dist/init/harnesses/codex.js +1 -1
- package/node_modules/@weavecode/cli/dist/init/harnesses/zcode.d.ts +29 -0
- package/node_modules/@weavecode/cli/dist/init/harnesses/zcode.js +1 -0
- package/node_modules/@weavecode/cli/dist/init/instruction-link.js +1 -1
- package/node_modules/@weavecode/cli/dist/init/plugin-templates.js +1 -1
- package/node_modules/@weavecode/cli/dist/init/prompt.js +4 -4
- package/node_modules/@weavecode/cli/dist/init/uninstall.js +6 -6
- package/node_modules/@weavecode/cli/dist/init/wiring.js +1 -1
- package/node_modules/@weavecode/cli/dist/parser.js +1 -1
- package/node_modules/@weavecode/cli/dist/statusline/ansi.js +1 -1
- package/node_modules/@weavecode/cli/dist/statusline/mock-input.js +1 -1
- package/node_modules/@weavecode/cli/dist/statusline/preview.js +2 -2
- package/node_modules/@weavecode/cli/package.json +5 -5
- package/node_modules/@weavecode/core/dist/harness-checks.js +1 -1
- package/node_modules/@weavecode/core/dist/index.d.ts +1 -1
- package/node_modules/@weavecode/core/dist/index.js +1 -1
- package/node_modules/@weavecode/core/dist/platform.js +1 -1
- package/node_modules/@weavecode/core/dist/types.d.ts +0 -8
- package/node_modules/@weavecode/core/dist/types.js +1 -1
- package/node_modules/@weavecode/core/dist/utils/fs.js +1 -1
- package/node_modules/@weavecode/core/dist/utils/logger.js +1 -1
- package/node_modules/@weavecode/core/package.json +1 -1
- package/node_modules/@weavecode/harness/dist/claude/assets.d.ts +10 -0
- package/node_modules/@weavecode/harness/dist/claude/assets.js +4 -1
- package/node_modules/@weavecode/harness/dist/claude/capture.d.ts +4 -2
- package/node_modules/@weavecode/harness/dist/claude/capture.js +15 -9
- package/node_modules/@weavecode/harness/dist/claude/detect.js +1 -1
- package/node_modules/@weavecode/harness/dist/claude/event-source.js +1 -1
- package/node_modules/@weavecode/harness/dist/claude/index.d.ts +6 -1
- package/node_modules/@weavecode/harness/dist/claude/index.js +1 -1
- package/node_modules/@weavecode/harness/dist/claude/mcp-gen.js +1 -1
- package/node_modules/@weavecode/harness/dist/claude/messaging.js +2 -2
- package/node_modules/@weavecode/harness/dist/claude/model-aliases.d.ts +14 -0
- package/node_modules/@weavecode/harness/dist/claude/model-aliases.js +1 -1
- package/node_modules/@weavecode/harness/dist/claude/peers.js +1 -1
- package/node_modules/@weavecode/harness/dist/claude/plan.d.ts +2 -0
- package/node_modules/@weavecode/harness/dist/claude/plan.js +1 -0
- package/node_modules/@weavecode/harness/dist/claude/plugins.js +4 -4
- package/node_modules/@weavecode/harness/dist/claude/prompt-inbox.js +1 -1
- package/node_modules/@weavecode/harness/dist/claude/prompt.js +3 -3
- package/node_modules/@weavecode/harness/dist/claude/questions.js +2 -2
- package/node_modules/@weavecode/harness/dist/claude/settings-gen.js +1 -1
- package/node_modules/@weavecode/harness/dist/claude/side-question.d.ts +12 -0
- package/node_modules/@weavecode/harness/dist/claude/side-question.js +1 -0
- package/node_modules/@weavecode/harness/dist/claude/statusline/config.d.ts +200 -0
- package/node_modules/@weavecode/harness/dist/claude/statusline/config.js +1 -0
- package/node_modules/@weavecode/harness/dist/claude/statusline/generator.d.ts +12 -0
- package/node_modules/@weavecode/harness/dist/claude/statusline/generator.js +745 -0
- package/node_modules/@weavecode/harness/dist/claude/statusline/git-cache.d.ts +10 -0
- package/node_modules/@weavecode/harness/dist/claude/statusline/git-cache.js +1 -0
- package/node_modules/@weavecode/harness/dist/claude/statusline/surface.d.ts +20 -0
- package/node_modules/@weavecode/harness/dist/claude/statusline/surface.js +2 -0
- package/node_modules/@weavecode/harness/dist/claude/transcript.js +3 -3
- package/node_modules/@weavecode/harness/dist/codex/assets.js +1 -1
- package/node_modules/@weavecode/harness/dist/codex/detect.js +1 -1
- package/node_modules/@weavecode/harness/dist/codex/index.d.ts +1 -0
- package/node_modules/@weavecode/harness/dist/codex/index.js +1 -1
- package/node_modules/@weavecode/harness/dist/codex/mcp.d.ts +30 -0
- package/node_modules/@weavecode/harness/dist/codex/mcp.js +1 -0
- package/node_modules/@weavecode/harness/dist/codex/messaging.js +1 -1
- package/node_modules/@weavecode/harness/dist/codex/plugin-state.js +1 -1
- package/node_modules/@weavecode/harness/dist/codex/toml.js +9 -9
- package/node_modules/@weavecode/harness/dist/driver.d.ts +110 -2
- package/node_modules/@weavecode/harness/dist/driver.js +1 -1
- package/node_modules/@weavecode/harness/dist/init/generated.js +2 -2
- package/node_modules/@weavecode/harness/dist/init/pipeline.js +1 -1
- package/node_modules/@weavecode/harness/dist/registry-wiring.js +1 -1
- package/node_modules/@weavecode/harness/dist/shared/install-link.js +1 -1
- package/node_modules/@weavecode/harness/dist/zcode/assets.d.ts +8 -0
- package/node_modules/@weavecode/harness/dist/zcode/assets.js +1 -0
- package/node_modules/@weavecode/harness/dist/zcode/capture.d.ts +36 -0
- package/node_modules/@weavecode/harness/dist/zcode/capture.js +3 -0
- package/node_modules/@weavecode/harness/dist/zcode/detect.d.ts +2 -0
- package/node_modules/@weavecode/harness/dist/zcode/detect.js +1 -0
- package/node_modules/@weavecode/harness/dist/zcode/event-source.d.ts +8 -0
- package/node_modules/@weavecode/harness/dist/zcode/event-source.js +1 -0
- package/node_modules/@weavecode/harness/dist/zcode/index.d.ts +13 -0
- package/node_modules/@weavecode/harness/dist/zcode/index.js +1 -0
- package/node_modules/@weavecode/harness/dist/zcode/mcp.d.ts +6 -0
- package/node_modules/@weavecode/harness/dist/zcode/mcp.js +2 -0
- package/node_modules/@weavecode/harness/dist/zcode/messaging.d.ts +13 -0
- package/node_modules/@weavecode/harness/dist/zcode/messaging.js +1 -0
- package/node_modules/@weavecode/harness/dist/zcode/resume.d.ts +7 -0
- package/node_modules/@weavecode/harness/dist/zcode/resume.js +1 -0
- package/node_modules/@weavecode/harness/package.json +2 -2
- package/node_modules/@weavecode/server/dist/daemon/dev-entry.js +1 -1
- package/node_modules/@weavecode/server/dist/daemon/hook-pipeline.d.ts +8 -1
- package/node_modules/@weavecode/server/dist/daemon/hook-pipeline.js +1 -1
- package/node_modules/@weavecode/server/dist/daemon/hooks-routes.js +1 -1
- package/node_modules/@weavecode/server/dist/daemon/server.d.ts +7 -0
- package/node_modules/@weavecode/server/dist/daemon/server.js +8 -6
- package/node_modules/@weavecode/server/dist/daemon/session-probe.d.ts +13 -0
- package/node_modules/@weavecode/server/dist/daemon/session-probe.js +1 -1
- package/node_modules/@weavecode/server/dist/daemon/snapshot-refresher.d.ts +7 -1
- package/node_modules/@weavecode/server/dist/daemon/snapshot-refresher.js +1 -1
- package/node_modules/@weavecode/server/dist/daemon/sse-hub.d.ts +40 -0
- package/node_modules/@weavecode/server/dist/daemon/sse-hub.js +1 -0
- package/node_modules/@weavecode/server/dist/daemon/start.js +1 -1
- package/node_modules/@weavecode/server/dist/db/db.js +20 -2
- package/node_modules/@weavecode/server/dist/hooks/bus.d.ts +5 -0
- package/node_modules/@weavecode/server/dist/hooks/inbox-worker.d.ts +8 -1
- package/node_modules/@weavecode/server/dist/hooks/inbox-worker.js +3 -3
- package/node_modules/@weavecode/server/dist/hooks/message-deliverer.d.ts +29 -0
- package/node_modules/@weavecode/server/dist/hooks/message-deliverer.js +2 -2
- package/node_modules/@weavecode/server/dist/hooks/mirror.d.ts +9 -4
- package/node_modules/@weavecode/server/dist/hooks/mirror.js +4 -4
- package/node_modules/@weavecode/server/dist/hooks/session-purge.d.ts +3 -0
- package/node_modules/@weavecode/server/dist/hooks/session-purge.js +1 -1
- package/node_modules/@weavecode/server/dist/hooks/side-questions.d.ts +41 -0
- package/node_modules/@weavecode/server/dist/hooks/side-questions.js +1 -0
- package/node_modules/@weavecode/server/dist/hooks/toolkit.js +5 -5
- package/node_modules/@weavecode/server/dist/install/apply-update.js +1 -1
- package/node_modules/@weavecode/server/dist/install/capabilities.d.ts +10 -0
- package/node_modules/@weavecode/server/dist/install/capabilities.js +1 -1
- package/node_modules/@weavecode/server/dist/install/codex-toml.d.ts +8 -26
- package/node_modules/@weavecode/server/dist/install/codex-toml.js +1 -1
- package/node_modules/@weavecode/server/dist/install/global-config.d.ts +2 -4
- package/node_modules/@weavecode/server/dist/install/global-config.js +1 -2
- package/node_modules/@weavecode/server/dist/install/installer.d.ts +3 -5
- package/node_modules/@weavecode/server/dist/install/installer.js +1 -3
- package/node_modules/@weavecode/server/dist/install/mcp-rows.d.ts +5 -5
- package/node_modules/@weavecode/server/dist/install/mcp-rows.js +1 -1
- package/node_modules/@weavecode/server/dist/registry.js +2 -2
- package/node_modules/@weavecode/server/dist/repositories/hook-events.d.ts +7 -0
- package/node_modules/@weavecode/server/dist/repositories/hook-events.js +2 -2
- package/node_modules/@weavecode/server/dist/repositories/sessions.d.ts +15 -4
- package/node_modules/@weavecode/server/dist/repositories/sessions.js +2 -2
- package/node_modules/@weavecode/server/dist/repositories/side-questions.d.ts +50 -0
- package/node_modules/@weavecode/server/dist/repositories/side-questions.js +6 -0
- package/node_modules/@weavecode/server/dist/statusline/config.d.ts +4 -198
- package/node_modules/@weavecode/server/dist/statusline/config.js +1 -1
- package/node_modules/@weavecode/server/dist/statusline/generator.d.ts +5 -0
- package/node_modules/@weavecode/server/dist/statusline/generator.js +1 -717
- package/node_modules/@weavecode/server/dist/statusline/manager.d.ts +5 -5
- package/node_modules/@weavecode/server/dist/statusline/manager.js +3 -4
- package/node_modules/@weavecode/server/dist/surfaces.d.ts +11 -1
- package/node_modules/@weavecode/server/dist/surfaces.js +1 -1
- package/node_modules/@weavecode/server/package.json +4 -4
- package/node_modules/@weavecode/server/web/dist/assets/index-CHWbLAje.js +594 -0
- package/node_modules/@weavecode/server/web/dist/assets/index-D0s5v8QS.css +11 -0
- package/node_modules/@weavecode/server/web/dist/index.html +2 -2
- package/node_modules/@weavecode/templates/package.json +2 -2
- package/package.json +6 -6
- package/node_modules/@weavecode/server/web/dist/assets/index-D5WFh77i.css +0 -11
- package/node_modules/@weavecode/server/web/dist/assets/index-DJibaGnJ.js +0 -585
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weavecode/harness",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Pluggable harness drivers — registry, init pipeline template method, and harness integration modules",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"node": ">=22.5.0"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@weavecode/core": "^0.1.
|
|
23
|
+
"@weavecode/core": "^0.1.2",
|
|
24
24
|
"smol-toml": "^1.3.0"
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import oe from"node:http";import{openDatabase as te}from"../db/db.js";import{ProjectRepository as re}from"../repositories/projects.js";import{HookEventRepository as ne}from"../repositories/hook-events.js";import{LibraryRepository as ie}from"../repositories/libraries.js";import{HookRuleRepository as x}from"../repositories/hook-rules.js";import{HookDispatchRepository as ae}from"../repositories/hook-dispatches.js";import{PluginStateRepository as v}from"../repositories/plugin-states.js";import{SessionRepository as le}from"../repositories/sessions.js";import{createRuntimeRegistry as ce}from"@weavecode/harness";import{TERMINAL_LOCATOR_ENV_KEYS as pe}from"@weavecode/core";import{HookRuleRegistry as me}from"../hooks/registry.js";import{HookDispatcher as ue}from"../hooks/dispatcher.js";import{HookEventBus as de}from"../hooks/bus.js";import{ResumeRunner as fe}from"../hooks/resume-runner.js";import{HeadlessRunner as he}from"../hooks/headless-runner.js";import{MessageDeliverer as we}from"../hooks/message-deliverer.js";import{QuestionTracker as Se}from"../hooks/question-tracker.js";import{SideQuestionRepository as ge}from"../repositories/side-questions.js";import{claimSideAnswer as ve}from"../hooks/side-questions.js";import{startPromptFlusher as be}from"../hooks/prompt-flusher.js";import{SessionWatchRepository as Re}from"../repositories/session-watches.js";import{PushSubscriptionRepository as ke}from"../repositories/push-subscriptions.js";import{UploadRepository as De}from"../repositories/uploads.js";import{PushNotifier as ye}from"../hooks/push-notify.js";import{SessionSequenceRepository as Ee}from"../repositories/session-sequences.js";import{WatchEngine as Pe}from"../hooks/watch-engine.js";import{SequenceEngine as Ie}from"../hooks/sequence-engine.js";import{loadAllPlugins as Te}from"../hooks/plugin-loader.js";import{ensureBuiltinMarket as Ae,builtinMarketDir as Ce}from"../market/builtin.js";import{ToolkitService as _e}from"../hooks/toolkit.js";import{startSessionSweeper as He}from"../hooks/session-sweeper.js";import{startStatuslineCacheSweeper as $e}from"../statusline/cache-sweeper.js";import{readStatuslineFacts as Oe}from"../statusline/manager.js";import{WatchService as Me}from"../watch/watch-service.js";import{weaveRequestHandler as qe}from"./server.js";import{startTlsServer as Le}from"./tls.js";import{DataBus as Fe}from"./data-bus.js";import{TerminalTopologyRefresher as Ne}from"./terminal-topology-refresher.js";import{SnapshotRefresher as H}from"./snapshot-refresher.js";import{SessionProbe as je,dbTranscriptPathResolver as Ue}from"./session-probe.js";import{InflightCountsRefresher as Be}from"./inflight-counts-refresher.js";import{ProjectUpdatesRefresher as Qe}from"./project-updates-refresher.js";import{FingerprintCache as We}from"../install/fingerprint.js";import{detectProjectUpdateCounts as Ge}from"../install/updates.js";import{claudeLivenessById as Ve,pidAliveOf as xe}from"./claude-liveness.js";import{promptTerminalChannel as Ke,readTerminalCounts as Ye}from"./opener.js";import{wireHookFilePipeline as ze}from"./hook-pipeline.js";import{DEFAULT_PORT as $,DAEMON_HOST as Je,DEFAULT_BIND_HOST as Xe,findFreePort as Ze,isPortFree as es}from"./port.js";import{writeDaemonState as K}from"./state.js";import{readDaemonSettingsSync as O,setDaemonSettingsDb as ss,DEFAULT_SERVER_SETTINGS as os}from"./settings.js";import{AuthService as ts}from"./dashboard-auth.js";import{resolveStaticDir as rs}from"./static.js";const ns="CLAUDE_CODE_CHILD_SESSION"in process.env;delete process.env.CLAUDE_CODE_CHILD_SESSION;for(const l of pe)delete process.env[l];async function is(){const l=await es($)?$:await Ze($+1);K({pid:process.pid,port:l,startedAt:new Date().toISOString()});const o=te();ss(o);const m=O().server?.bindHost??Xe,b=O().server?.tlsPort??os.tlsPort,R=new ts;R.initialPassword&&console.log(`[dev] initial dashboard password: ${R.initialPassword}`);const r=new re(o),u=new ne(o),p=new ie(o),k=ce(),Y=k.byCapability("events").flatMap(e=>e.events??[]),f=new me(new x(o)),h=new ae(o),n=new le(o),t=new de;{const e=await Ae({libraries:p});e.error?console.error(`[dev] builtin market unavailable: ${e.error}`):e.changed&&console.log(`[dev] builtin market ${e.form==="link"?"linked":"copied"} -> ${Ce(p.dataDir)}`)}const{loader:c}=await Te({rules:new x(o),registry:f,states:new v(o),bus:t,libraries:p},r.list());{const e=c.list().filter(s=>s.status==="rejected");console.log(`[dev] plugins loaded: ${c.list().length} (rejected: ${e.length})`);for(const s of e)console.log(`[dev] rejected ${s.id}: ${s.error}`)}c.watchRoots();const M={list:()=>c.list(),setPluginEnabled:(e,s)=>c.setPluginEnabled(e,s),reloadAll:()=>c.reloadAll(),rescan:()=>c.rescanRoots(r.list(),p)},z=new _e({projects:r,states:new v(o),plugins:M,libraries:p}),q=new ue({registry:f,dispatches:h,bus:t,projects:r,sessions:n,plugins:c,pluginStates:new v(o)}),D=new he,J=new fe({dispatches:h,sessions:n,projects:r,bus:t,resumeSpec:k.get("claude")?.resume,headless:D}),a=new we({sessions:n,projects:r,hookEvents:u,bus:t,headless:D,terminal:Ke(()=>O().terminal)});be(a);const y=new Re(o),E=new Pe({watches:y,deliverer:a,sessions:n,bus:t}),L=new ke(o),P=new De(o),X=new ye({subscriptions:L,sessions:n,bus:t}),I=new Ee(o),w=new Ie({sequences:I,deliverer:a,sessions:n,bus:t});w.rearm();const S=new Se({sessions:n,hookEvents:u,onPending:e=>{t.emit({kind:"question.pending",sessionId:e.sessionId,harness:e.harness,count:e.questions.length,at:e.at})}});S.seedFromDb();const F=new ge(o),N={repo:F,bus:t},j=new Me(r);j.start();const i=new Fe,g=new je(Ue(u)),T=new Ne(async()=>({counts:await Ye(r.list()),terminals:await a.sessionTerminals()}),i,{shouldRun:()=>i.hasSubscribers(),graceMs:1e4});T.start();const A=new H(()=>{const e=Ve(),s={};for(const d of n.listWithCounts()){const V=xe(d,e);V!==null&&(s[d.sessionId]=V)}return{alive:s}},i,{key:"sessionLiveness",shouldRun:()=>i.hasSubscribers()});A.start();const C=new H(()=>{const e={};for(const s of n.listWithCounts()){if(s.turnOpen!==!0)continue;const d=g.interruptedAt(s.sessionId,s.lastSeenAt);d!==null&&(e[s.sessionId]=d)}return{interrupted:e}},i,{key:"sessionInterrupt",intervalMs:5e3,shouldRun:()=>i.hasSubscribers()});C.start(),new H(()=>{const e={};for(const s of r.list())e[s.id]=Oe(s.path);return e},i,{key:"statusline",intervalMs:5e3,shouldRun:()=>i.hasSubscribers(),invalidateOnly:!0}).start();const Z=new Be(()=>a.listInflightCounts(),i,t),ee=new We,se=new Qe(()=>Ge(r.list(),p.list(),ee),i,t,()=>i.hasSubscribers());He({sessions:n,events:u,dispatches:h,deliverer:a,watches:y,sequences:I,watchEngine:E,sequenceEngine:w,questions:S,sideQuestions:F,probe:g,uploads:P}),$e();const U=ze({projects:r,rules:f,sessions:n,bus:t,dispatcher:q,watchEngine:E,sequenceEngine:w,messagePipe:a,questionTracker:S,sideQuestions:{onEvent:e=>{const s=ve(N,e);s&&a.interrupt(s.sessionId)}},sessionProbe:g,uploads:P}),_=rs(),B=qe({projects:r,port:l,tlsPort:b,watch:j,hookEvents:u,adapters:Y,harnesses:k,libraries:p,staticDir:_,hookRegistry:f,hookDispatches:h,hookSessions:n,hookBus:t,dataBus:i,terminalRefresher:T,interruptRefresher:C,livenessRefresher:A,sessionProbe:g,inflightRefresher:Z,updatesRefresher:se,hookDispatcher:q,hookRunner:J,messageDeliverer:a,uploads:P,questionTracker:S,sideQuestions:N,sessionWatches:y,watchEngine:E,sessionSequences:I,sequenceEngine:w,pushSubscriptions:L,pushNotifier:X,hookPlugins:M,hookMirror:U.mirror,pluginStates:new v(o),toolkit:z,auth:R}),Q=oe.createServer(B);let W=null;Le({port:b,bindHost:m,handler:B}).then(e=>{W=e,e&&console.log(`[dev] weave daemon tls listening on https://${m}:${e.port}`)});const G=()=>{T.stop(),A.stop(),C.stop(),D.killAll(),U.stop(),Q.close(),W?.server.close(),process.exit(0)};process.on("SIGTERM",G),process.on("SIGINT",G),Q.listen(l,m,()=>{K({pid:process.pid,port:l,startedAt:new Date().toISOString(),host:m,tlsPort:b}),console.log(`[dev] weave daemon listening on http://${m}:${l}`),console.log(`[dev] cli/dashboard local url: http://${Je}:${l}`),console.log(`[dev] child-session marker stripped: ${ns}`),console.log(_?`[dev] static: ${_}`:"[dev] static: (web/dist not built \u2014 run pnpm build:web)")})}is();
|
|
@@ -5,6 +5,7 @@ import type { SequenceEngine } from '../hooks/sequence-engine.js';
|
|
|
5
5
|
import { MirrorWriter } from '../hooks/mirror.js';
|
|
6
6
|
import type { MessageDeliverer } from '../hooks/message-deliverer.js';
|
|
7
7
|
import type { QuestionTracker } from '../hooks/question-tracker.js';
|
|
8
|
+
import type { SideQuestionEvent } from '../hooks/side-questions.js';
|
|
8
9
|
import type { HookRuleRegistry } from '../hooks/registry.js';
|
|
9
10
|
import type { ProjectRepository } from '../repositories/projects.js';
|
|
10
11
|
import type { SessionRepository } from '../repositories/sessions.js';
|
|
@@ -22,10 +23,16 @@ export interface HookPipelineDeps {
|
|
|
22
23
|
messagePipe?: Pick<MessageDeliverer, 'hasQueued' | 'flushSession'>;
|
|
23
24
|
/** 阻塞式问答跟踪(AskUserQuestion/计划/权限确认):ingest 事件派生待答面。 */
|
|
24
25
|
questionTracker?: Pick<QuestionTracker, 'onEvent'>;
|
|
25
|
-
/**
|
|
26
|
+
/** 旁路会话回答认领(claude = /btw):同透传(判据归 harness 能力面)。 */
|
|
27
|
+
sideQuestions?: {
|
|
28
|
+
onEvent(input: SideQuestionEvent): void;
|
|
29
|
+
};
|
|
30
|
+
/** 会话转录路径上报(中断态探测;同 pane env 口径)+ 换模型落定(`PostModelSwitch`)+ 推理档位。 */
|
|
26
31
|
sessionProbe?: {
|
|
27
32
|
noteTranscript(sessionId: string, path: string | null): void;
|
|
28
33
|
noteModel(sessionId: string, model: string): void;
|
|
34
|
+
sessionStarted(sessionId: string, model: string | null): void;
|
|
35
|
+
noteEffort(sessionId: string, level: string): void;
|
|
29
36
|
};
|
|
30
37
|
/** 上传件落库面:回执命中时把消息的随附图挂到开轮事件(对话气泡缩略图)。 */
|
|
31
38
|
uploads?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readdirSync as f,unlinkSync as a}from"node:fs";import{join as c}from"node:path";import{getDbPath as m}from"../db/paths.js";import{MirrorWriter as b,hookShardId as l,hooksRoot as h,mirrorPathOf as g,outboxPathOf as x}from"../hooks/mirror.js";import{promptboxPathOf as P}from"../hooks/promptbox.js";import{startInboxWorker as j,handleIngestedRecords as k,unparkFailedInbox as $,INBOX_CLAIM_SUFFIX as
|
|
1
|
+
import{readdirSync as f,unlinkSync as a}from"node:fs";import{join as c}from"node:path";import{getDbPath as m}from"../db/paths.js";import{MirrorWriter as b,hookShardId as l,hooksRoot as h,mirrorPathOf as g,outboxPathOf as x}from"../hooks/mirror.js";import{promptboxPathOf as P}from"../hooks/promptbox.js";import{startInboxWorker as j,handleIngestedRecords as k,unparkFailedInbox as $,INBOX_CLAIM_SUFFIX as u,INBOX_FAILED_SUFFIX as p}from"../hooks/inbox-worker.js";function w(e,o){return[c(e,"inbox",`${o}.jsonl`),c(e,"inbox",`${o}${u}`),c(e,"inbox",`${o}${p}`),g(e,o),x(e,o),P(e,o)]}export function purgeProjectPipeline(e,o){for(const n of w(e,l(o)))try{a(n)}catch{}}function F(e){return(e.endsWith(".tmp")?e.slice(0,-4):e).replace(new RegExp(`(${u}|${p})$`),"").replace(/\.(jsonl|json)$/,"")}const v=/^[0-9a-f]{16}$/;export function purgeOrphanPipelineFiles(e){const o=new Set(e.projects.list().map(r=>l(r.token)));let n=0;for(const r of["inbox","mirror","outbox","promptbox"]){let t;try{t=f(c(e.hooksDir,r))}catch{continue}for(const i of t){const s=F(i);if(!(!v.test(s)||o.has(s)))try{a(c(e.hooksDir,r,i)),n++}catch{}}}return n}export function wireHookFilePipeline(e){const o=h(),n=purgeOrphanPipelineFiles({projects:e.projects,hooksDir:o});n>0&&console.log(`[weave] hook pipeline: purged ${n} orphan file(s)`);const r=$(o);r>0&&console.log(`[weave] hook pipeline: revived ${r} parked inbox file(s)`);const t=new b({projects:e.projects,rules:e.rules,sessions:e.sessions});e.rules.onChange=()=>t.syncAll(),t.attachToBus(e.bus),t.syncAll();const i=j({dbPath:m(),hooksDir:o});return i.on("message",s=>{k({projects:e.projects,hooksDir:o,bus:e.bus,dispatcher:e.dispatcher,watchEngine:e.watchEngine,sequenceEngine:e.sequenceEngine,messagePipe:e.messagePipe,questionTracker:e.questionTracker,sideQuestions:e.sideQuestions,sessionProbe:e.sessionProbe,uploads:e.uploads},s)}),i.on("error",s=>console.error("[weave] inbox worker failed:",s)),{mirror:t,stop:()=>i.terminate().then(()=>{})}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{stat as V}from"node:fs/promises";import{z as i}from"zod";import{HARNESS_SESSION_CONTROL as J,HookRuleFilterSchema as Y,HookRuleSourceSchema as X,HandlerConfigSchema as Z,HookDispatchStatusSchema as q,isHarness as w}from"@weavecode/core";import{aliasOfModel as ee,modelAliasTargets as te}from"@weavecode/harness";import{readJson as k,sendJson as r}from"./http.js";import{installPlugin as re,uninstallPlugin as oe}from"../hooks/plugin-installer.js";import{purgeSession as ne}from"../hooks/session-purge.js";import{composeAnswerAtoms as se}from"../hooks/answer-keys.js";import{effective as ie,setProjectEnabled as ae}from"../hooks/plugin-project-state.js";import{readDaemonSettings as ue,readDaemonSettingsSync as le,readHooksEnabled as ce}from"./settings.js";import{defaultProjectOpener as me,terminalBackendFor as de}from"./opener.js";import{claudeLivenessById as F,pidAliveOf as K}from"./claude-liveness.js";import{captureSurfaces as he,messagingSurfaceFor as E,messagingSurfaces as ge}from"../surfaces.js";import{getDataDir as fe}from"../db/paths.js";import{UPLOAD_MAX_BYTES as be,UPLOAD_MEDIA_TYPES as ye}from"../repositories/uploads.js";import{WEAVE_VERSION as pe}from"../version.js";import{DEFAULT_WATCH_ON as ke}from"../repositories/session-watches.js";import{SEQUENCE_HOP_LIMIT as z}from"../hooks/sequence-engine.js";const Q=i.object({name:i.string().min(1),enabled:i.boolean().optional(),priority:i.number().int().optional(),projectId:i.number().int().nullable(),source:X,filter:Y.optional(),handler:Z,timeoutMs:i.number().int().positive().optional(),record:i.boolean().optional()}),ve=Q.partial(),Ie=i.object({enabled:i.boolean()}),Se=i.object({enabled:i.boolean(),projectId:i.number().int().optional()}),we=i.object({title:i.string().min(1).max(200)}),Pe=i.object({sessionId:i.string().min(1)}),Ee=i.object({text:i.string().max(2e4),fromKind:i.enum(["dashboard","session"]).optional(),fromSessionId:i.string().min(1).nullable().optional(),images:i.array(i.string().regex(/^[a-f0-9]{64}$/)).max(8).optional(),forceTerminal:i.boolean().optional()}),Te=i.object({name:i.string().min(1).max(200),mediaType:i.enum(ye),data:i.string().min(1)});function je(s){return{id:s.id,name:s.name,mediaType:s.mediaType,size:s.size}}const Re=i.object({answers:i.array(i.object({questionKey:i.string().min(1),options:i.array(i.number().int().min(0)).max(32),text:i.string().max(2e3).optional()})).min(1)}),Me=i.object({watcherSessionId:i.string().min(1).nullable().optional(),notify:i.string().min(1).max(2e4).nullable().optional(),on:i.array(i.enum(["idle","turnEnd","messageFailed"])).min(1).optional(),times:i.number().int().min(1).max(100).nullable().optional()}),De=i.object({watchId:i.number().int().positive()}),Oe=i.object({model:i.string().min(1).max(128)}),Be=i.object({endpoint:i.string().url(),keys:i.object({p256dh:i.string().min(1),auth:i.string().min(1)})}),Ce=i.object({endpoint:i.string().url()}),$e=i.object({name:i.string().max(200).optional(),steps:i.array(i.object({to:i.string().min(1),text:i.string().min(1).max(2e4),mode:i.enum(["prompt","inject","headless","auto"]).optional()})).min(1)}),Ae=i.object({src:i.string().min(1)});function T(s,e){return s.prompt!==null||e}function xe(s){const{diag:e,capture:t,eventCount:l}=s,g=[];return!t||t.source==="none"||!t.present?g.push("no-hook-script"):t.weaveOwned?t.reportsInbox||g.push("hook-script-stale"):g.push("hook-script-not-weave"),l===0&&g.push("no-events"),e&&(e.alive===!1&&g.push("not-running"),e.inbox.reason==="no-credentials"&&t?.reportsInbox===!0&&g.push("no-inbox-credentials"),e.inbox.reason==="auth-token-required"&&g.push("inbox-token-required"),e.relay.reason==="no-peer-name"&&g.push("no-peer-name"),e.terminal.reason==="no-backend"&&g.push("terminal-no-backend"),e.terminal.reason==="no-pane-env"&&g.push("terminal-no-pane-env"),e.pending>0&&g.push("queued"),e.stalled&&g.push("stalled")),g}export async function handleHooksRoutes(s,e,t,l,g){if(!(l.startsWith("/hooks/")||l==="/sessions"||l.startsWith("/sessions/")||l.startsWith("/messaging")||l==="/uploads"||l.startsWith("/images/")))return!1;if(l==="/hooks/rules"){if(!t.hookRegistry)return r(e,503,{error:"Hooks subsystem unavailable"}),!0;if(s.method==="GET"){const o=g.searchParams.get("projectId"),n=o!=null&&o!==""?Number(o):void 0;return r(e,200,{rules:t.hookRegistry.list(n)}),!0}if(s.method==="POST"){try{const o=Q.parse(await k(s)),n=t.hookRegistry.create(o);r(e,201,{rule:n})}catch(o){r(e,400,{error:o instanceof Error?o.message:"Invalid rule body"})}return!0}}const j=l.match(/^\/hooks\/rules\/([^/]+)$/);if(j){if(!t.hookRegistry)return r(e,503,{error:"Hooks subsystem unavailable"}),!0;const o=decodeURIComponent(j[1]);if(s.method==="PUT"){try{const n=ve.parse(await k(s)),a=t.hookRegistry.update(o,n);if(!a)return r(e,404,{error:"Rule not found"}),!0;r(e,200,{rule:a})}catch(n){r(e,400,{error:n instanceof Error?n.message:"Invalid rule body"})}return!0}if(s.method==="DELETE")return t.hookRegistry.remove(o)?(r(e,200,{ok:!0}),!0):(r(e,404,{error:"Rule not found"}),!0)}const R=l.match(/^\/hooks\/rules\/([^/]+)\/toggle$/);if(R&&s.method==="POST"){if(!t.hookRegistry)return r(e,503,{error:"Hooks subsystem unavailable"}),!0;try{const o=Ie.parse(await k(s)),n=t.hookRegistry.setEnabled(decodeURIComponent(R[1]),o.enabled);if(!n)return r(e,404,{error:"Rule not found"}),!0;r(e,200,{rule:n})}catch(o){r(e,400,{error:o instanceof Error?o.message:"Invalid body"})}return!0}if(l==="/hooks/plugins"){if(s.method==="GET"){const o=g.searchParams.get("projectId");if(o==null||o==="")return r(e,200,{plugins:t.hookPlugins?.list()??[]}),!0;const n=t.projects.getById(Number(o));if(!n)return r(e,404,{error:"Project not found"}),!0;if(!t.pluginStates)return r(e,503,{error:"Plugin states unavailable"}),!0;const a=t.pluginStates;return r(e,200,{plugins:(t.hookPlugins?.list()??[]).map(u=>{if(u.status==="rejected")return u;const c=ie(n,u.id,{states:a})?"active":"disabled";return c===u.status?u:{...u,status:c}})}),!0}if(s.method==="POST"){if(!t.hookPlugins)return r(e,503,{error:"Plugin loader unavailable"}),!0;try{const o=Ae.parse(await k(s)),n=await re(o.src);await t.hookPlugins.reloadAll(),r(e,201,n)}catch(o){r(e,400,{error:o instanceof Error?o.message:"Install failed"})}return!0}}const M=l.match(/^\/hooks\/plugins\/([^/]+)\/uninstall$/);if(M&&s.method==="POST"){if(!t.hookPlugins)return r(e,503,{error:"Plugin loader unavailable"}),!0;const o=decodeURIComponent(M[1]);return await oe(o)?(await t.hookPlugins.reloadAll(),r(e,200,{ok:!0}),!0):(r(e,404,{error:"Plugin directory not found"}),!0)}if(l==="/hooks/plugins/rescan"&&s.method==="POST"){if(!t.hookPlugins)return r(e,503,{error:"Plugin loader unavailable"}),!0;try{await t.hookPlugins.rescan(),r(e,200,{ok:!0,plugins:t.hookPlugins.list().length})}catch(o){r(e,500,{error:o instanceof Error?o.message:"Rescan failed"})}return!0}const D=l.match(/^\/hooks\/plugins\/([^/]+)\/toggle$/);if(D&&s.method==="POST"){if(!t.hookPlugins)return r(e,503,{error:"Plugin loader unavailable"}),!0;try{const o=Se.parse(await k(s)),n=decodeURIComponent(D[1]);if(o.projectId!==void 0){const u=t.projects.getById(o.projectId);return u?t.hookPlugins.list().some(c=>c.id===n)?(ae(u,n,o.enabled,{projects:t.projects}),r(e,200,{ok:!0}),!0):(r(e,404,{error:"Plugin not found"}),!0):(r(e,404,{error:"Project not found"}),!0)}if(!t.hookPlugins.setPluginEnabled(n,o.enabled))return r(e,404,{error:"Plugin not found"}),!0;r(e,200,{ok:!0})}catch(o){r(e,400,{error:o instanceof Error?o.message:"Invalid body"})}return!0}if(l==="/hooks/dispatches"&&s.method==="GET"){if(!t.hookDispatches)return r(e,503,{error:"Hooks subsystem unavailable"}),!0;const o=g.searchParams.get("ruleId")??void 0,n=g.searchParams.get("status"),a=n?q.parse(n):void 0,u=g.searchParams.get("sessionId")??void 0,c=g.searchParams.get("limit"),d=c?Number(c):void 0;return r(e,200,{dispatches:t.hookDispatches.list({ruleId:o,status:a,sessionId:u,limit:Number.isFinite(d)?d:void 0})}),!0}const O=l.match(/^\/hooks\/dispatches\/(\d+)\/run$/);if(O&&s.method==="POST"){if(!t.hookRunner)return r(e,503,{error:"Resume runner unavailable"}),!0;let o;try{o=Pe.parse(await k(s)).sessionId}catch(a){return r(e,400,{error:a instanceof Error?a.message:"Invalid body"}),!0}const n=await t.hookRunner.run(Number(O[1]),o);return n.ok?r(e,200,{ok:!0,dispatchId:n.dispatchId}):r(e,n.status,{error:n.error}),!0}if(l==="/sessions"&&s.method==="GET"){if(!t.hookSessions)return r(e,503,{error:"Hooks subsystem unavailable"}),!0;const o=g.searchParams.get("projectId"),n=o!=null&&o!==""?Number(o):void 0,a=t.hookSessions.listWithCounts(n),u=F(),c=t.terminalRefresher?.lastSnapshot?.terminals??{},d={};for(const h of a){const b=h.harness??"";b in d||(d[b]=te(J[h.harness]?.modelNames??[]))}return r(e,200,{sessions:a.map(h=>{const b=t.sessionProbe?.currentModel(h.sessionId)??null;return{...h,pidAlive:K(h,u),terminal:c[h.sessionId]??null,interruptedAt:h.turnOpen===!0?t.sessionProbe?.interruptedAt(h.sessionId,h.lastSeenAt)??null:null,model:b,modelAlias:ee(d[h.harness??""]??[],b),mode:t.sessionProbe?.currentMode(h.sessionId)??null,transcriptPresent:t.sessionProbe?.transcriptPresent(h.sessionId)??null}}),modelCatalog:d}),!0}const S=l.match(/^\/sessions\/([^/]+)$/);if(S&&s.method==="PATCH"){if(!t.hookSessions)return r(e,503,{error:"Hooks subsystem unavailable"}),!0;const o=decodeURIComponent(S[1]);if(!t.hookSessions.getBySessionId(o))return r(e,404,{error:"Session not found"}),!0;try{const n=we.parse(await k(s)),a=t.hookSessions.setPendingTitle(o,n.title);a?.projectId!=null&&t.hookMirror?.syncProject(a.projectId),r(e,200,{session:a})}catch(n){r(e,400,{error:n instanceof Error?n.message:"Invalid body"})}return!0}if(S&&s.method==="DELETE"){if(!t.hookSessions||!t.hookEvents)return r(e,503,{error:"Hooks subsystem unavailable"}),!0;const o=decodeURIComponent(S[1]),n=t.hookSessions.getBySessionId(o);if(!n)return r(e,404,{error:"Session not found"}),!0;const a=ne({sessions:t.hookSessions,events:t.hookEvents,dispatches:t.hookDispatches,deliverer:t.messageDeliverer,watches:t.sessionWatches,questions:t.questionTracker,probe:t.sessionProbe,uploads:t.uploads},o);return n.projectId!=null&&t.hookMirror?.syncProject(n.projectId),r(e,200,{ok:!0,sessionId:o,...a}),!0}const B=l.match(/^\/sessions\/([^/]+)\/open-terminal$/);if(B&&s.method==="POST"){const o=decodeURIComponent(B[1]);if(!t.hookSessions)return r(e,503,{error:"Hooks subsystem unavailable"}),!0;const n=t.hookSessions.getBySessionId(o);if(!n)return r(e,404,{error:"Session not found"}),!0;if(!t.hookEvents)return r(e,503,{error:"Hook ingestion unavailable"}),!0;const a=t.hookEvents.listBySession(o,1),u=a[a.length-1]?.source;if(!w(u))return r(e,400,{error:"Session has no hook events to determine its harness"}),!0;const c=t.harnesses?.get(u)?.resume;if(!c?.supported||!c.openArgs)return r(e,501,{error:`Harness "${u}" does not support interactive resume`}),!0;if(!n.projectId)return r(e,400,{error:"Session is not bound to a project"}),!0;const d=t.projects.getById(n.projectId);if(!d)return r(e,404,{error:"Project not found"}),!0;if(t.sessionProbe?.transcriptPresent(o)===!1)return r(e,409,{error:"Session transcript is missing \u2014 nothing to resume"}),!0;const h=t.opener??me;let b;if(s.method==="POST")try{const y=await k(s);typeof y?.target=="string"&&y.target&&(b=y.target)}catch{}try{if(!(await V(d.path)).isDirectory())throw new Error(`Not a directory: ${d.path}`);const m=await ue(),f=[c.bin,...c.openArgs(o)];if(b){if(!de(m.terminal).enumerable)throw new Error(`Terminal preset "${m.terminal.preset}" cannot attach to a chosen pane \u2014 pick wezterm, wt or Terminal.app in settings`);const p=h.runInTerminalAt;if(!p)throw new Error("Targeted attach is unavailable");await p(d.path,f,b,m.terminal)}else await h.runInTerminal(d.path,f,m.terminal);setTimeout(()=>{t.terminalRefresher?.refreshNow()},600),r(e,200,{ok:!0,sessionId:o,command:f.join(" "),cwd:d.path,...b?{target:b}:{}})}catch(y){r(e,400,{error:y instanceof Error?y.message:"Open failed"})}return!0}if(l==="/uploads"&&s.method==="POST"){if(!t.uploads)return r(e,503,{error:"Upload subsystem unavailable"}),!0;let o;try{o=Te.parse(await k(s))}catch(u){return r(e,400,{error:u instanceof Error?u.message:"Invalid body"}),!0}const n=Buffer.from(o.data,"base64");if(n.length===0)return r(e,400,{error:"Empty image"}),!0;if(n.length>be)return r(e,413,{error:"Image too large"}),!0;const a=t.uploads.save({name:o.name,mediaType:o.mediaType,bytes:n});return r(e,201,{upload:je(a)}),!0}const C=l.match(/^\/images\/([a-f0-9]{64})$/);if(C&&s.method==="GET"){if(!t.uploads)return r(e,503,{error:"Upload subsystem unavailable"}),!0;const o=t.uploads.fileOf(C[1]);return o?(e.writeHead(200,{"content-type":o.row.mediaType,"content-length":String(o.bytes.length),"cache-control":"private, max-age=31536000, immutable"}),e.end(o.bytes),!0):(r(e,404,{error:"Image not found"}),!0)}const $=l.match(/^\/sessions\/([^/]+)\/messages$/);if($){const o=decodeURIComponent($[1]);if(s.method==="GET")return t.messageDeliverer?t.hookSessions?.getBySessionId(o)?(r(e,200,{messages:t.messageDeliverer.listVisible(o)}),!0):(r(e,404,{error:"Session not found"}),!0):(r(e,503,{error:"Messaging subsystem unavailable"}),!0);if(s.method==="POST"){if(!t.messageDeliverer||!t.hookSessions)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;let n;try{n=Ee.parse(await k(s))}catch(d){return r(e,400,{error:d instanceof Error?d.message:"Invalid body"}),!0}const a=n.images??[];if(!n.text.trim()&&a.length===0)return r(e,400,{error:"Message is empty"}),!0;let u=[];if(a.length>0){const d=t.uploads?.refsOf(a)??null;if(!d)return r(e,404,{error:"Image not found"}),!0;u=d}const c=await t.messageDeliverer.send({toSessionId:o,text:n.text,...u.length>0?{images:u}:{},...n.forceTerminal===!0?{forceTerminal:!0}:{},mode:"prompt",fromKind:n.fromKind??"dashboard",fromSessionId:n.fromSessionId??null,oneWay:!0});return c.ok?r(e,201,{ok:!0,messageId:c.messageId,status:c.status,message:c.message}):r(e,c.status,{error:c.error}),!0}}const A=l.match(/^\/sessions\/([^/]+)\/messages\/([^/]+)\/resend$/);if(A&&s.method==="POST"){if(!t.messageDeliverer)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;const o=await t.messageDeliverer.resend(decodeURIComponent(A[2]));return o.ok?r(e,200,{ok:!0,messageId:o.messageId,status:o.status,message:o.message}):r(e,o.status,{error:o.error}),!0}const P=l.match(/^\/sessions\/([^/]+)\/messages\/([^/]+)\/dismiss$/);if(P&&s.method==="POST"){if(!t.messageDeliverer)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;const o=t.messageDeliverer.dismiss(decodeURIComponent(P[1]),decodeURIComponent(P[2]));return o.ok?r(e,200,{ok:!0}):r(e,o.status,{error:o.error}),!0}if(l==="/messaging/questions"&&s.method==="GET")return t.questionTracker?(r(e,200,{questions:t.questionTracker.snapshot()}),!0):(r(e,503,{error:"Question tracking unavailable"}),!0);const x=l.match(/^\/sessions\/([^/]+)\/answer$/);if(x&&s.method==="POST"){if(!t.questionTracker||!t.messageDeliverer)return r(e,503,{error:"Question tracking unavailable"}),!0;const o=decodeURIComponent(x[1]);let n;try{n=Re.parse(await k(s))}catch(d){return r(e,400,{error:d instanceof Error?d.message:"Invalid body"}),!0}const a=t.questionTracker.get(o);if(!a)return r(e,409,{error:"Session has no pending question"}),!0;const u=se(a.questions,n.answers);if(!u.ok)return r(e,400,{error:u.error}),!0;const c=await t.messageDeliverer.answer(o,u.atoms);return c.ok?(t.questionTracker.markAnswered(o),r(e,200,{ok:!0,sessionId:o}),!0):(r(e,c.status,{error:c.error}),!0)}const H=l.match(/^\/sessions\/([^/]+)\/interrupt$/);if(H&&s.method==="POST"){if(!t.messageDeliverer)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;const o=decodeURIComponent(H[1]),n=await t.messageDeliverer.interrupt(o);return n.ok?(setTimeout(()=>t.interruptRefresher?.tick(),1500),r(e,200,{ok:!0,sessionId:o}),!0):(r(e,n.status,{error:n.error}),!0)}const U=l.match(/^\/sessions\/([^/]+)\/close-pane$/);if(U&&s.method==="POST"){if(!t.messageDeliverer)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;const o=decodeURIComponent(U[1]),n=await t.messageDeliverer.closePane(o);return n.ok?(setTimeout(()=>{t.livenessRefresher?.tick(),t.terminalRefresher?.refreshNow()},600),r(e,200,{ok:!0,sessionId:o}),!0):(r(e,n.status,{error:n.error}),!0)}const W=l.match(/^\/sessions\/([^/]+)\/mode$/);if(W&&s.method==="POST"){if(!t.messageDeliverer)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;const o=decodeURIComponent(W[1]),n=await t.messageDeliverer.cycleMode(o);return n.ok?(r(e,200,{ok:!0,sessionId:o}),!0):(r(e,n.status,{error:n.error}),!0)}const N=l.match(/^\/sessions\/([^/]+)\/model$/);if(N&&s.method==="POST"){if(!t.messageDeliverer)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;const o=decodeURIComponent(N[1]),n=Oe.parse(await k(s)),a=await t.messageDeliverer.setModel(o,n.model);return a.ok?(setTimeout(()=>t.interruptRefresher?.tick(),1500),r(e,200,{ok:!0,sessionId:o}),!0):(r(e,a.status,{error:a.error}),!0)}const _=l.match(/^\/sessions\/([^/]+)\/watch$/);if(_){const o=decodeURIComponent(_[1]);if(s.method==="POST"){if(!t.sessionWatches||!t.hookSessions||!t.hookEvents)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;let n;try{n=Me.parse(await k(s))}catch(p){return r(e,400,{error:p instanceof Error?p.message:"Invalid body"}),!0}if(!t.hookSessions.getBySessionId(o))return r(e,404,{error:"Session not found"}),!0;const u=t.hookEvents.listBySession(o,1),c=u[u.length-1]?.source;if(!w(c))return r(e,400,{error:"Session has no hook events to determine its harness"}),!0;const d=E(c),h=n.on??[...ke];if((h.includes("idle")||h.includes("turnEnd"))&&d.idleSignals.length===0)return r(e,409,{error:d.unsupportedReasons?.watch??`Harness "${c}" does not support idle watching`}),!0;if(h.includes("messageFailed")&&!(T(d,t.messageDeliverer?.hasTerminalChannel()??!1)||d.inject!==null||d.headless!==null))return r(e,409,{error:d.unsupportedReasons?.prompt??`Harness "${c}" supports no messaging channel`}),!0;if(n.watcherSessionId){if(!t.hookSessions.getBySessionId(n.watcherSessionId))return r(e,404,{error:"Watcher session not found"}),!0;const v=t.hookEvents.listBySession(n.watcherSessionId,1),I=v[v.length-1]?.source;if(!w(I))return r(e,400,{error:"Watcher session has no hook events to determine its harness"}),!0;const L=E(I);if(!L.inject)return r(e,409,{error:L.unsupportedReasons?.inject??`Harness "${I}" cannot receive watch notifications`}),!0}const b=n.times===void 0?1:n.times,y=(n.watcherSessionId??null)===null?t.sessionWatches.listPendingByTarget(o).find(p=>p.watcherSessionId===null):void 0,m=y?t.sessionWatches.updateById(y.id,{onEvents:h,remaining:b}):void 0,f=m??t.sessionWatches.insert({watcherSessionId:n.watcherSessionId??null,targetSessionId:o,notify:n.notify??null,onEvents:h,times:b});return t.watchEngine?.created(f),r(e,m?200:201,{ok:!0,watchId:f.id,watch:f}),!0}if(s.method==="DELETE"){if(!t.sessionWatches)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;let n;try{n=De.parse(await k(s))}catch(u){return r(e,400,{error:u instanceof Error?u.message:"Invalid body"}),!0}const a=t.sessionWatches.getById(n.watchId);return!a||a.targetSessionId!==o||a.firedAt!==null?(r(e,404,{error:"Watch not found"}),!0):(t.sessionWatches.deleteById(n.watchId),r(e,200,{ok:!0,watchId:n.watchId}),!0)}}if(l==="/messaging/watches"&&s.method==="GET")return t.sessionWatches?(r(e,200,{watches:t.sessionWatches.listPending()}),!0):(r(e,503,{error:"Messaging subsystem unavailable"}),!0);if(l==="/messaging/push/key"&&s.method==="GET")return t.pushNotifier?(r(e,200,{publicKey:t.pushNotifier.publicKey()}),!0):(r(e,503,{error:"Push subsystem unavailable"}),!0);if(l==="/messaging/push/subscribe"&&s.method==="POST"){if(!t.pushSubscriptions)return r(e,503,{error:"Push subsystem unavailable"}),!0;try{const o=Be.parse(await k(s)),n=t.pushSubscriptions.upsert({endpoint:o.endpoint,p256dh:o.keys.p256dh,auth:o.keys.auth,userAgent:s.headers["user-agent"]??null});r(e,201,{ok:!0,subscription:n})}catch(o){r(e,400,{error:o instanceof Error?o.message:"Invalid body"})}return!0}if(l==="/messaging/push/unsubscribe"&&s.method==="POST"){if(!t.pushSubscriptions)return r(e,503,{error:"Push subsystem unavailable"}),!0;try{const o=Ce.parse(await k(s));t.pushSubscriptions.deleteByEndpoint(o.endpoint),r(e,200,{ok:!0})}catch(o){r(e,400,{error:o instanceof Error?o.message:"Invalid body"})}return!0}if(l==="/messaging/sequences"){if(s.method==="GET")return t.sessionSequences?(r(e,200,{sequences:t.sessionSequences.list()}),!0):(r(e,503,{error:"Messaging subsystem unavailable"}),!0);if(s.method==="POST"){if(!t.sessionSequences||!t.sequenceEngine||!t.hookSessions||!t.hookEvents)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;let o;try{o=$e.parse(await k(s))}catch(u){return r(e,400,{error:u instanceof Error?u.message:"Invalid body"}),!0}if(o.steps.length>z)return r(e,400,{error:`Sequences are capped at ${z} steps (hop limit)`}),!0;const n=t.messageDeliverer?.hasTerminalChannel()??!1;for(let u=0;u<o.steps.length;u++){const c=o.steps[u];if(!t.hookSessions.getBySessionId(c.to))return r(e,404,{error:`Step ${u+1}: session not found`}),!0;const h=t.hookEvents.listBySession(c.to,1),b=h[h.length-1]?.source;if(!w(b))return r(e,400,{error:`Step ${u+1}: session has no hook events to determine its harness`}),!0;const y=E(b),m=c.mode??"auto";if(!(m==="prompt"?T(y,n):m==="inject"?y.inject!==null:m==="headless"?y.headless!==null:y.inject!==null||y.headless!==null)){const p=m==="auto"?y.unsupportedReasons?.inject??`Harness "${b}" supports no messaging channel`:y.unsupportedReasons?.[m]??`Harness "${b}" does not support the ${m} channel`;return r(e,409,{error:`Step ${u+1}: ${p}`}),!0}}const a=t.sessionSequences.insert({name:o.name??"",steps:o.steps.map(u=>({...u,mode:u.mode??"auto"}))});return await t.sequenceEngine.trigger(a),r(e,201,{ok:!0,sequence:t.sessionSequences.getById(a.id)??a}),!0}}const G=l.match(/^\/messaging\/sequences\/(\d+)$/);if(G&&s.method==="DELETE"){if(!t.sessionSequences)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;const o=t.sessionSequences.getById(Number(G[1]));return o?o.status!=="running"?(r(e,409,{error:`Sequence already ${o.status}`}),!0):(t.sequenceEngine?t.sequenceEngine.cancel(o.id):t.sessionSequences.finish(o.id,"cancelled"),r(e,200,{ok:!0,sequenceId:o.id}),!0):(r(e,404,{error:"Sequence not found"}),!0)}if(l==="/messaging/capabilities"&&s.method==="GET"){const o=t.messageDeliverer?.hasTerminalChannel()??!1;return r(e,200,{capabilities:ge().map(({harness:n,surface:a})=>({harness:n,discovery:a.discovery,prompt:T(a,o),inject:a.inject!==null,headless:a.headless!==null,watch:a.idleSignals.length>0,imageAttach:a.imageAttach!==null,unsupportedReasons:a.unsupportedReasons??null}))}),!0}if(l==="/messaging/diagnostics"&&s.method==="GET"){if(!t.messageDeliverer||!t.hookSessions)return r(e,503,{error:"Message subsystem unavailable"}),!0;const o=g.searchParams.get("session"),n=g.searchParams.get("project"),a=he(),u=t.projects.list(),c=await Promise.all(u.map(async m=>({id:m.id,name:m.name,path:m.path,captures:await Promise.all(a.map(async({harness:f,surface:p})=>({harness:f,...await p.scriptHealth({projectPath:m.path})})))}))),d=(m,f)=>c.find(p=>p.id===m)?.captures.find(p=>p.harness===f)??null,h=F(),y=t.hookSessions.listWithCounts(o?void 0:n?Number(n):void 0).filter(m=>o?m.sessionId===o:K(m,h)!==!1).map(m=>{const f=t.messageDeliverer.diagnose(m.sessionId),p=d(m.projectId,m.harness??null),v={inbox:f.inbox,relay:f.relay,terminal:f.terminal};return{sessionId:m.sessionId,projectId:m.projectId,harness:m.harness??null,title:m.title,lastSeenAt:m.lastSeenAt,eventCount:m.eventCount??0,lastEventType:m.lastEventType??null,alive:f.alive,paneEnvReported:f.paneEnvReported,channels:v,queue:{pending:f.pending,stalled:f.stalled},reachable:Object.values(v).some(I=>I.status==="ready"),hints:xe({diag:f,capture:p,eventCount:m.eventCount??0})}});return r(e,200,{daemon:{version:pe,platform:process.platform,dataDir:fe(),uptimeMs:Math.round(process.uptime()*1e3),terminalPreset:le().terminal.preset,terminalTextChannel:t.messageDeliverer.hasTerminalChannel(),hooksEnabled:ce()},projects:c,sessions:y}),!0}return!1}
|
|
1
|
+
import{readFileSync as ee}from"node:fs";import{stat as te}from"node:fs/promises";import{z as a}from"zod";import{HARNESS_SESSION_CONTROL as re,HookRuleFilterSchema as ne,HookRuleSourceSchema as oe,HandlerConfigSchema as se,HookDispatchStatusSchema as ie,isHarness as S}from"@weavecode/core";import{aliasOfModel as ae,modelAliasTargets as ue}from"@weavecode/harness";import{readJson as k,sendJson as r}from"./http.js";import{installPlugin as le,uninstallPlugin as ce}from"../hooks/plugin-installer.js";import{purgeSession as me}from"../hooks/session-purge.js";import{composeAnswerAtoms as de}from"../hooks/answer-keys.js";import{effective as he,setProjectEnabled as fe}from"../hooks/plugin-project-state.js";import{readDaemonSettings as ge,readDaemonSettingsSync as be,readHooksEnabled as ye}from"./settings.js";import{defaultProjectOpener as pe,terminalBackendFor as ke}from"./opener.js";import{claudeLivenessById as z,pidAliveOf as J}from"./claude-liveness.js";import{blockingPromptFor as Ie,captureSurfaces as Se,messagingSurfaceFor as E,messagingSurfaces as ve}from"../surfaces.js";import{getDataDir as we}from"../db/paths.js";import{UPLOAD_MAX_BYTES as Pe,UPLOAD_MEDIA_TYPES as Ee}from"../repositories/uploads.js";import{WEAVE_VERSION as Te}from"../version.js";import{DEFAULT_WATCH_ON as je}from"../repositories/session-watches.js";import{SEQUENCE_HOP_LIMIT as V}from"../hooks/sequence-engine.js";import{readSideQuestions as Re}from"../hooks/side-questions.js";function X(s,e){return s!==!1?s:e===!0?!1:null}const Y=20,Me=2e5;function De(s){try{const e=JSON.parse(s);return[typeof e.toolName=="string"?e.toolName:null,e.toolInput??null,e.toolResponse??null]}catch{return[null,null,null]}}function Z(s){if(!s)return null;try{const e=ee(s,"utf8");return e.trim()?e:null}catch{return null}}const q=a.object({name:a.string().min(1),enabled:a.boolean().optional(),priority:a.number().int().optional(),projectId:a.number().int().nullable(),source:oe,filter:ne.optional(),handler:se,timeoutMs:a.number().int().positive().optional(),record:a.boolean().optional()}),Oe=q.partial(),Be=a.object({enabled:a.boolean()}),Ce=a.object({enabled:a.boolean(),projectId:a.number().int().optional()}),Ae=a.object({title:a.string().min(1).max(200)}),$e=a.object({question:a.string().min(1).max(4e3)}),xe=a.object({sessionId:a.string().min(1)}),He=a.object({text:a.string().max(2e4),fromKind:a.enum(["dashboard","session"]).optional(),fromSessionId:a.string().min(1).nullable().optional(),images:a.array(a.string().regex(/^[a-f0-9]{64}$/)).max(8).optional(),forceTerminal:a.boolean().optional()}),Ue=a.object({name:a.string().min(1).max(200),mediaType:a.enum(Ee),data:a.string().min(1)});function Ne(s){return{id:s.id,name:s.name,mediaType:s.mediaType,size:s.size}}const We=a.object({answers:a.array(a.object({questionKey:a.string().min(1),options:a.array(a.number().int().min(0)).max(32),text:a.string().max(2e3).optional()})).min(1)}),_e=a.object({watcherSessionId:a.string().min(1).nullable().optional(),notify:a.string().min(1).max(2e4).nullable().optional(),on:a.array(a.enum(["idle","turnEnd","messageFailed"])).min(1).optional(),times:a.number().int().min(1).max(100).nullable().optional()}),Qe=a.object({watchId:a.number().int().positive()}),Fe=a.object({model:a.string().min(1).max(128)}),Ge=a.object({endpoint:a.string().url(),keys:a.object({p256dh:a.string().min(1),auth:a.string().min(1)})}),Le=a.object({endpoint:a.string().url()}),Ke=a.object({name:a.string().max(200).optional(),steps:a.array(a.object({to:a.string().min(1),text:a.string().min(1).max(2e4),mode:a.enum(["prompt","inject","headless","auto"]).optional()})).min(1)}),ze=a.object({src:a.string().min(1)});function T(s,e){return s.prompt!==null||e}function Je(s){const{diag:e,capture:t,eventCount:c}=s,f=[];return!t||t.source==="none"||!t.present?f.push("no-hook-script"):t.weaveOwned?t.reportsInbox||f.push("hook-script-stale"):f.push("hook-script-not-weave"),c===0&&f.push("no-events"),e&&(e.alive===!1&&f.push("not-running"),e.inbox.reason==="no-credentials"&&t?.reportsInbox===!0&&f.push("no-inbox-credentials"),e.inbox.reason==="auth-token-required"&&f.push("inbox-token-required"),e.relay.reason==="no-peer-name"&&f.push("no-peer-name"),e.terminal.reason==="no-backend"&&f.push("terminal-no-backend"),e.terminal.reason==="no-pane-env"&&f.push("terminal-no-pane-env"),e.pending>0&&f.push("queued"),e.stalled&&f.push("stalled")),f}export async function handleHooksRoutes(s,e,t,c,f){if(!(c.startsWith("/hooks/")||c==="/sessions"||c.startsWith("/sessions/")||c.startsWith("/messaging")||c==="/uploads"||c.startsWith("/images/")))return!1;if(c==="/hooks/rules"){if(!t.hookRegistry)return r(e,503,{error:"Hooks subsystem unavailable"}),!0;if(s.method==="GET"){const n=f.searchParams.get("projectId"),o=n!=null&&n!==""?Number(n):void 0;return r(e,200,{rules:t.hookRegistry.list(o)}),!0}if(s.method==="POST"){try{const n=q.parse(await k(s)),o=t.hookRegistry.create(n);r(e,201,{rule:o})}catch(n){r(e,400,{error:n instanceof Error?n.message:"Invalid rule body"})}return!0}}const j=c.match(/^\/hooks\/rules\/([^/]+)$/);if(j){if(!t.hookRegistry)return r(e,503,{error:"Hooks subsystem unavailable"}),!0;const n=decodeURIComponent(j[1]);if(s.method==="PUT"){try{const o=Oe.parse(await k(s)),i=t.hookRegistry.update(n,o);if(!i)return r(e,404,{error:"Rule not found"}),!0;r(e,200,{rule:i})}catch(o){r(e,400,{error:o instanceof Error?o.message:"Invalid rule body"})}return!0}if(s.method==="DELETE")return t.hookRegistry.remove(n)?(r(e,200,{ok:!0}),!0):(r(e,404,{error:"Rule not found"}),!0)}const R=c.match(/^\/hooks\/rules\/([^/]+)\/toggle$/);if(R&&s.method==="POST"){if(!t.hookRegistry)return r(e,503,{error:"Hooks subsystem unavailable"}),!0;try{const n=Be.parse(await k(s)),o=t.hookRegistry.setEnabled(decodeURIComponent(R[1]),n.enabled);if(!o)return r(e,404,{error:"Rule not found"}),!0;r(e,200,{rule:o})}catch(n){r(e,400,{error:n instanceof Error?n.message:"Invalid body"})}return!0}if(c==="/hooks/plugins"){if(s.method==="GET"){const n=f.searchParams.get("projectId");if(n==null||n==="")return r(e,200,{plugins:t.hookPlugins?.list()??[]}),!0;const o=t.projects.getById(Number(n));if(!o)return r(e,404,{error:"Project not found"}),!0;if(!t.pluginStates)return r(e,503,{error:"Plugin states unavailable"}),!0;const i=t.pluginStates;return r(e,200,{plugins:(t.hookPlugins?.list()??[]).map(u=>{if(u.status==="rejected")return u;const m=he(o,u.id,{states:i})?"active":"disabled";return m===u.status?u:{...u,status:m}})}),!0}if(s.method==="POST"){if(!t.hookPlugins)return r(e,503,{error:"Plugin loader unavailable"}),!0;try{const n=ze.parse(await k(s)),o=await le(n.src);await t.hookPlugins.reloadAll(),r(e,201,o)}catch(n){r(e,400,{error:n instanceof Error?n.message:"Install failed"})}return!0}}const M=c.match(/^\/hooks\/plugins\/([^/]+)\/uninstall$/);if(M&&s.method==="POST"){if(!t.hookPlugins)return r(e,503,{error:"Plugin loader unavailable"}),!0;const n=decodeURIComponent(M[1]);return await ce(n)?(await t.hookPlugins.reloadAll(),r(e,200,{ok:!0}),!0):(r(e,404,{error:"Plugin directory not found"}),!0)}if(c==="/hooks/plugins/rescan"&&s.method==="POST"){if(!t.hookPlugins)return r(e,503,{error:"Plugin loader unavailable"}),!0;try{await t.hookPlugins.rescan(),r(e,200,{ok:!0,plugins:t.hookPlugins.list().length})}catch(n){r(e,500,{error:n instanceof Error?n.message:"Rescan failed"})}return!0}const D=c.match(/^\/hooks\/plugins\/([^/]+)\/toggle$/);if(D&&s.method==="POST"){if(!t.hookPlugins)return r(e,503,{error:"Plugin loader unavailable"}),!0;try{const n=Ce.parse(await k(s)),o=decodeURIComponent(D[1]);if(n.projectId!==void 0){const u=t.projects.getById(n.projectId);return u?t.hookPlugins.list().some(m=>m.id===o)?(fe(u,o,n.enabled,{projects:t.projects}),r(e,200,{ok:!0}),!0):(r(e,404,{error:"Plugin not found"}),!0):(r(e,404,{error:"Project not found"}),!0)}if(!t.hookPlugins.setPluginEnabled(o,n.enabled))return r(e,404,{error:"Plugin not found"}),!0;r(e,200,{ok:!0})}catch(n){r(e,400,{error:n instanceof Error?n.message:"Invalid body"})}return!0}if(c==="/hooks/dispatches"&&s.method==="GET"){if(!t.hookDispatches)return r(e,503,{error:"Hooks subsystem unavailable"}),!0;const n=f.searchParams.get("ruleId")??void 0,o=f.searchParams.get("status"),i=o?ie.parse(o):void 0,u=f.searchParams.get("sessionId")??void 0,m=f.searchParams.get("limit"),d=m?Number(m):void 0;return r(e,200,{dispatches:t.hookDispatches.list({ruleId:n,status:i,sessionId:u,limit:Number.isFinite(d)?d:void 0})}),!0}const O=c.match(/^\/hooks\/dispatches\/(\d+)\/run$/);if(O&&s.method==="POST"){if(!t.hookRunner)return r(e,503,{error:"Resume runner unavailable"}),!0;let n;try{n=xe.parse(await k(s)).sessionId}catch(i){return r(e,400,{error:i instanceof Error?i.message:"Invalid body"}),!0}const o=await t.hookRunner.run(Number(O[1]),n);return o.ok?r(e,200,{ok:!0,dispatchId:o.dispatchId}):r(e,o.status,{error:o.error}),!0}if(c==="/sessions"&&s.method==="GET"){if(!t.hookSessions)return r(e,503,{error:"Hooks subsystem unavailable"}),!0;const n=f.searchParams.get("projectId"),o=n!=null&&n!==""?Number(n):void 0,i=t.hookSessions.listWithCounts(o),u=z(),m=t.terminalRefresher?.lastSnapshot?.terminals??{},d={};for(const h of i){const y=h.harness??"";y in d||(d[y]=ue(re[h.harness]?.modelNames??[]))}return r(e,200,{sessions:i.map(h=>{const y=t.sessionProbe?.currentModel(h.sessionId)??null,b=t.sessionProbe?.transcriptPresent(h.sessionId)??null,l=S(h.harness)?t.harnesses?.get(h.harness)?.resume:void 0;return{...h,pidAlive:J(h,u),canOpenTerminal:!!(l?.supported&&l.openArgs),terminal:m[h.sessionId]??null,interruptedAt:h.turnOpen===!0?t.sessionProbe?.interruptedAt(h.sessionId,h.lastSeenAt)??null:null,model:y,modelAlias:ae(d[h.harness??""]??[],y),mode:t.sessionProbe?.currentMode(h.sessionId)??null,effort:t.sessionProbe?.currentEffort(h.sessionId)??null,transcriptPresent:X(b,h.hasRun)}}),modelCatalog:d}),!0}const w=c.match(/^\/sessions\/([^/]+)$/);if(w&&s.method==="PATCH"){if(!t.hookSessions)return r(e,503,{error:"Hooks subsystem unavailable"}),!0;const n=decodeURIComponent(w[1]);if(!t.hookSessions.getBySessionId(n))return r(e,404,{error:"Session not found"}),!0;try{const o=Ae.parse(await k(s)),i=t.hookSessions.setPendingTitle(n,o.title);i?.projectId!=null&&t.hookMirror?.syncProject(i.projectId),r(e,200,{session:i})}catch(o){r(e,400,{error:o instanceof Error?o.message:"Invalid body"})}return!0}if(w&&s.method==="DELETE"){if(!t.hookSessions||!t.hookEvents)return r(e,503,{error:"Hooks subsystem unavailable"}),!0;const n=decodeURIComponent(w[1]),o=t.hookSessions.getBySessionId(n);if(!o)return r(e,404,{error:"Session not found"}),!0;const i=me({sessions:t.hookSessions,events:t.hookEvents,dispatches:t.hookDispatches,deliverer:t.messageDeliverer,watches:t.sessionWatches,sequences:t.sessionSequences,questions:t.questionTracker,sideQuestions:t.sideQuestions?.repo,probe:t.sessionProbe,uploads:t.uploads},n);return o.projectId!=null&&t.hookMirror?.syncProject(o.projectId),r(e,200,{ok:!0,sessionId:n,...i}),!0}const B=c.match(/^\/sessions\/([^/]+)\/open-terminal$/);if(B&&s.method==="POST"){const n=decodeURIComponent(B[1]);if(!t.hookSessions)return r(e,503,{error:"Hooks subsystem unavailable"}),!0;const o=t.hookSessions.getBySessionId(n);if(!o)return r(e,404,{error:"Session not found"}),!0;if(!t.hookEvents)return r(e,503,{error:"Hook ingestion unavailable"}),!0;const i=t.hookEvents.listBySession(n,1),u=i[i.length-1]?.source;if(!S(u))return r(e,400,{error:"Session has no hook events to determine its harness"}),!0;const m=t.harnesses?.get(u)?.resume;if(!m?.supported||!m.openArgs)return r(e,501,{error:`Harness "${u}" does not support interactive resume`}),!0;if(!o.projectId)return r(e,400,{error:"Session is not bound to a project"}),!0;const d=t.projects.getById(o.projectId);if(!d)return r(e,404,{error:"Project not found"}),!0;const h=t.sessionProbe?.transcriptPresent(n)??null;if(X(h,t.hookSessions.hasRunOf(n))===!1)return r(e,409,{error:"Session transcript is missing \u2014 nothing to resume"}),!0;const y=t.opener??pe;let b;if(s.method==="POST")try{const l=await k(s);typeof l?.target=="string"&&l.target&&(b=l.target)}catch{}try{if(!(await te(d.path)).isDirectory())throw new Error(`Not a directory: ${d.path}`);const g=await ge(),p=[m.bin,...m.openArgs(n)];if(b){if(!ke(g.terminal).enumerable)throw new Error(`Terminal preset "${g.terminal.preset}" cannot attach to a chosen pane \u2014 pick wezterm, wt or Terminal.app in settings`);const I=y.runInTerminalAt;if(!I)throw new Error("Targeted attach is unavailable");await I(d.path,p,b,g.terminal)}else await y.runInTerminal(d.path,p,g.terminal);setTimeout(()=>{t.terminalRefresher?.refreshNow()},600),r(e,200,{ok:!0,sessionId:n,command:p.join(" "),cwd:d.path,...b?{target:b}:{}})}catch(l){r(e,400,{error:l instanceof Error?l.message:"Open failed"})}return!0}if(c==="/uploads"&&s.method==="POST"){if(!t.uploads)return r(e,503,{error:"Upload subsystem unavailable"}),!0;let n;try{n=Ue.parse(await k(s))}catch(u){return r(e,400,{error:u instanceof Error?u.message:"Invalid body"}),!0}const o=Buffer.from(n.data,"base64");if(o.length===0)return r(e,400,{error:"Empty image"}),!0;if(o.length>Pe)return r(e,413,{error:"Image too large"}),!0;const i=t.uploads.save({name:n.name,mediaType:n.mediaType,bytes:o});return r(e,201,{upload:Ne(i)}),!0}const C=c.match(/^\/images\/([a-f0-9]{64})$/);if(C&&s.method==="GET"){if(!t.uploads)return r(e,503,{error:"Upload subsystem unavailable"}),!0;const n=t.uploads.fileOf(C[1]);return n?(e.writeHead(200,{"content-type":n.row.mediaType,"content-length":String(n.bytes.length),"cache-control":"private, max-age=31536000, immutable"}),e.end(n.bytes),!0):(r(e,404,{error:"Image not found"}),!0)}const A=c.match(/^\/sessions\/([^/]+)\/messages$/);if(A){const n=decodeURIComponent(A[1]);if(s.method==="GET")return t.messageDeliverer?t.hookSessions?.getBySessionId(n)?(r(e,200,{messages:t.messageDeliverer.listVisible(n)}),!0):(r(e,404,{error:"Session not found"}),!0):(r(e,503,{error:"Messaging subsystem unavailable"}),!0);if(s.method==="POST"){if(!t.messageDeliverer||!t.hookSessions)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;let o;try{o=He.parse(await k(s))}catch(d){return r(e,400,{error:d instanceof Error?d.message:"Invalid body"}),!0}const i=o.images??[];if(!o.text.trim()&&i.length===0)return r(e,400,{error:"Message is empty"}),!0;let u=[];if(i.length>0){const d=t.uploads?.refsOf(i)??null;if(!d)return r(e,404,{error:"Image not found"}),!0;u=d}const m=await t.messageDeliverer.send({toSessionId:n,text:o.text,...u.length>0?{images:u}:{},...o.forceTerminal===!0?{forceTerminal:!0}:{},mode:"prompt",fromKind:o.fromKind??"dashboard",fromSessionId:o.fromSessionId??null,oneWay:!0});return m.ok?r(e,201,{ok:!0,messageId:m.messageId,status:m.status,message:m.message}):r(e,m.status,{error:m.error}),!0}}const $=c.match(/^\/sessions\/([^/]+)\/plan$/);if($&&s.method==="GET"){const n=decodeURIComponent($[1]);if(!t.hookEvents)return r(e,503,{error:"Hook ingestion unavailable"}),!0;const o=t.hookEvents.listBySession(n,1),i=o[o.length-1]?.source,u=S(i)?Ie(i)?.plan??null:null;if(!u)return r(e,200,{plan:null}),!0;const m=b=>b.flatMap(l=>{const g=u.draftOf(...De(l.payload));return g?[{row:l,draft:g}]:[]}),[d]=m(t.hookEvents.listByTools(n,[u.tool],null,Y));if(!d)return r(e,200,{plan:null}),!0;const[h]=m(t.hookEvents.listByTools(n,u.writeTools,u.fileHint,Y)).filter(b=>b.row.id<d.row.id),y=d.draft.text??Z(d.draft.filePath)??h?.draft.text??Z(h?.draft.filePath??null);return y?(r(e,200,{plan:{eventId:d.row.id,at:d.row.createdAt,text:y.slice(0,Me)}}),!0):(r(e,200,{plan:null}),!0)}const x=c.match(/^\/sessions\/([^/]+)\/messages\/([^/]+)\/resend$/);if(x&&s.method==="POST"){if(!t.messageDeliverer)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;const n=await t.messageDeliverer.resend(decodeURIComponent(x[2]));return n.ok?r(e,200,{ok:!0,messageId:n.messageId,status:n.status,message:n.message}):r(e,n.status,{error:n.error}),!0}const P=c.match(/^\/sessions\/([^/]+)\/messages\/([^/]+)\/dismiss$/);if(P&&s.method==="POST"){if(!t.messageDeliverer)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;const n=t.messageDeliverer.dismiss(decodeURIComponent(P[1]),decodeURIComponent(P[2]));return n.ok?r(e,200,{ok:!0}):r(e,n.status,{error:n.error}),!0}if(c==="/messaging/questions"&&s.method==="GET")return t.questionTracker?(r(e,200,{questions:t.questionTracker.snapshot()}),!0):(r(e,503,{error:"Question tracking unavailable"}),!0);const H=c.match(/^\/sessions\/([^/]+)\/answer$/);if(H&&s.method==="POST"){if(!t.questionTracker||!t.messageDeliverer)return r(e,503,{error:"Question tracking unavailable"}),!0;const n=decodeURIComponent(H[1]);let o;try{o=We.parse(await k(s))}catch(d){return r(e,400,{error:d instanceof Error?d.message:"Invalid body"}),!0}const i=t.questionTracker.get(n);if(!i)return r(e,409,{error:"Session has no pending question"}),!0;const u=de(i.questions,o.answers);if(!u.ok)return r(e,400,{error:u.error}),!0;const m=await t.messageDeliverer.answer(n,u.atoms);return m.ok?(t.questionTracker.markAnswered(n),r(e,200,{ok:!0,sessionId:n}),!0):(r(e,m.status,{error:m.error}),!0)}const U=c.match(/^\/sessions\/([^/]+)\/interrupt$/);if(U&&s.method==="POST"){if(!t.messageDeliverer)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;const n=decodeURIComponent(U[1]),o=await t.messageDeliverer.interrupt(n);return o.ok?(setTimeout(()=>t.interruptRefresher?.tick(),1500),r(e,200,{ok:!0,sessionId:n}),!0):(r(e,o.status,{error:o.error}),!0)}const N=c.match(/^\/sessions\/([^/]+)\/close-pane$/);if(N&&s.method==="POST"){if(!t.messageDeliverer)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;const n=decodeURIComponent(N[1]),o=await t.messageDeliverer.closePane(n);return o.ok?(setTimeout(()=>{t.livenessRefresher?.tick(),t.terminalRefresher?.refreshNow()},600),r(e,200,{ok:!0,sessionId:n}),!0):(r(e,o.status,{error:o.error}),!0)}const W=c.match(/^\/sessions\/([^/]+)\/mode$/);if(W&&s.method==="POST"){if(!t.messageDeliverer)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;const n=decodeURIComponent(W[1]),o=await t.messageDeliverer.cycleMode(n);return o.ok?(r(e,200,{ok:!0,sessionId:n}),!0):(r(e,o.status,{error:o.error}),!0)}const _=c.match(/^\/sessions\/([^/]+)\/model$/);if(_&&s.method==="POST"){if(!t.messageDeliverer)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;const n=decodeURIComponent(_[1]),o=Fe.parse(await k(s)),i=await t.messageDeliverer.setModel(n,o.model);return i.ok?(setTimeout(()=>t.interruptRefresher?.tick(),1500),r(e,200,{ok:!0,sessionId:n}),!0):(r(e,i.status,{error:i.error}),!0)}const Q=c.match(/^\/sessions\/([^/]+)\/side-question$/);if(Q&&s.method==="POST"){if(!t.messageDeliverer||!t.sideQuestions)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;const n=decodeURIComponent(Q[1]);let o;try{o=$e.parse(await k(s))}catch(m){return r(e,400,{error:m instanceof Error?m.message:"Invalid body"}),!0}const i=await t.messageDeliverer.askSideQuestion(n,o.question);if(!i.ok)return r(e,i.status,{error:i.error}),!0;const u=t.sideQuestions.repo.insert({sessionId:n,projectId:t.hookSessions?.getBySessionId(n)?.projectId??null,question:i.question});return r(e,200,{ok:!0,sideQuestion:u}),!0}const F=c.match(/^\/sessions\/([^/]+)\/side-questions$/);if(F&&s.method==="GET"){if(!t.sideQuestions)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;const n=decodeURIComponent(F[1]),o=Number(f.searchParams.get("limit")),i=Number(f.searchParams.get("beforeId")),u=Re(t.sideQuestions,n,{limit:Number.isFinite(o)&&o>0?Math.min(o,100):20,beforeId:Number.isFinite(i)&&i>0?i:void 0});return r(e,200,{sideQuestions:u}),!0}const G=c.match(/^\/sessions\/([^/]+)\/watch$/);if(G){const n=decodeURIComponent(G[1]);if(s.method==="POST"){if(!t.sessionWatches||!t.hookSessions||!t.hookEvents)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;let o;try{o=_e.parse(await k(s))}catch(p){return r(e,400,{error:p instanceof Error?p.message:"Invalid body"}),!0}if(!t.hookSessions.getBySessionId(n))return r(e,404,{error:"Session not found"}),!0;const u=t.hookEvents.listBySession(n,1),m=u[u.length-1]?.source;if(!S(m))return r(e,400,{error:"Session has no hook events to determine its harness"}),!0;const d=E(m),h=o.on??[...je];if((h.includes("idle")||h.includes("turnEnd"))&&d.idleSignals.length===0)return r(e,409,{error:d.unsupportedReasons?.watch??`Harness "${m}" does not support idle watching`}),!0;if(h.includes("messageFailed")&&!(T(d,t.messageDeliverer?.hasTerminalChannel()??!1)||d.inject!==null||d.headless!==null))return r(e,409,{error:d.unsupportedReasons?.prompt??`Harness "${m}" supports no messaging channel`}),!0;if(o.watcherSessionId){if(!t.hookSessions.getBySessionId(o.watcherSessionId))return r(e,404,{error:"Watcher session not found"}),!0;const I=t.hookEvents.listBySession(o.watcherSessionId,1),v=I[I.length-1]?.source;if(!S(v))return r(e,400,{error:"Watcher session has no hook events to determine its harness"}),!0;const K=E(v);if(!K.inject)return r(e,409,{error:K.unsupportedReasons?.inject??`Harness "${v}" cannot receive watch notifications`}),!0}const y=o.times===void 0?1:o.times,b=(o.watcherSessionId??null)===null?t.sessionWatches.listPendingByTarget(n).find(p=>p.watcherSessionId===null):void 0,l=b?t.sessionWatches.updateById(b.id,{onEvents:h,remaining:y}):void 0,g=l??t.sessionWatches.insert({watcherSessionId:o.watcherSessionId??null,targetSessionId:n,notify:o.notify??null,onEvents:h,times:y});return t.watchEngine?.created(g),r(e,l?200:201,{ok:!0,watchId:g.id,watch:g}),!0}if(s.method==="DELETE"){if(!t.sessionWatches)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;let o;try{o=Qe.parse(await k(s))}catch(u){return r(e,400,{error:u instanceof Error?u.message:"Invalid body"}),!0}const i=t.sessionWatches.getById(o.watchId);return!i||i.targetSessionId!==n||i.firedAt!==null?(r(e,404,{error:"Watch not found"}),!0):(t.sessionWatches.deleteById(o.watchId),r(e,200,{ok:!0,watchId:o.watchId}),!0)}}if(c==="/messaging/watches"&&s.method==="GET")return t.sessionWatches?(r(e,200,{watches:t.sessionWatches.listPending()}),!0):(r(e,503,{error:"Messaging subsystem unavailable"}),!0);if(c==="/messaging/push/key"&&s.method==="GET")return t.pushNotifier?(r(e,200,{publicKey:t.pushNotifier.publicKey()}),!0):(r(e,503,{error:"Push subsystem unavailable"}),!0);if(c==="/messaging/push/subscribe"&&s.method==="POST"){if(!t.pushSubscriptions)return r(e,503,{error:"Push subsystem unavailable"}),!0;try{const n=Ge.parse(await k(s)),o=t.pushSubscriptions.upsert({endpoint:n.endpoint,p256dh:n.keys.p256dh,auth:n.keys.auth,userAgent:s.headers["user-agent"]??null});r(e,201,{ok:!0,subscription:o})}catch(n){r(e,400,{error:n instanceof Error?n.message:"Invalid body"})}return!0}if(c==="/messaging/push/unsubscribe"&&s.method==="POST"){if(!t.pushSubscriptions)return r(e,503,{error:"Push subsystem unavailable"}),!0;try{const n=Le.parse(await k(s));t.pushSubscriptions.deleteByEndpoint(n.endpoint),r(e,200,{ok:!0})}catch(n){r(e,400,{error:n instanceof Error?n.message:"Invalid body"})}return!0}if(c==="/messaging/sequences"){if(s.method==="GET")return t.sessionSequences?(r(e,200,{sequences:t.sessionSequences.list()}),!0):(r(e,503,{error:"Messaging subsystem unavailable"}),!0);if(s.method==="POST"){if(!t.sessionSequences||!t.sequenceEngine||!t.hookSessions||!t.hookEvents)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;let n;try{n=Ke.parse(await k(s))}catch(u){return r(e,400,{error:u instanceof Error?u.message:"Invalid body"}),!0}if(n.steps.length>V)return r(e,400,{error:`Sequences are capped at ${V} steps (hop limit)`}),!0;const o=t.messageDeliverer?.hasTerminalChannel()??!1;for(let u=0;u<n.steps.length;u++){const m=n.steps[u];if(!t.hookSessions.getBySessionId(m.to))return r(e,404,{error:`Step ${u+1}: session not found`}),!0;const h=t.hookEvents.listBySession(m.to,1),y=h[h.length-1]?.source;if(!S(y))return r(e,400,{error:`Step ${u+1}: session has no hook events to determine its harness`}),!0;const b=E(y),l=m.mode??"auto";if(!(l==="prompt"?T(b,o):l==="inject"?b.inject!==null:l==="headless"?b.headless!==null:b.inject!==null||b.headless!==null)){const p=l==="auto"?b.unsupportedReasons?.inject??`Harness "${y}" supports no messaging channel`:b.unsupportedReasons?.[l]??`Harness "${y}" does not support the ${l} channel`;return r(e,409,{error:`Step ${u+1}: ${p}`}),!0}}const i=t.sessionSequences.insert({name:n.name??"",steps:n.steps.map(u=>({...u,mode:u.mode??"auto"}))});return await t.sequenceEngine.trigger(i),r(e,201,{ok:!0,sequence:t.sessionSequences.getById(i.id)??i}),!0}}const L=c.match(/^\/messaging\/sequences\/(\d+)$/);if(L&&s.method==="DELETE"){if(!t.sessionSequences)return r(e,503,{error:"Messaging subsystem unavailable"}),!0;const n=t.sessionSequences.getById(Number(L[1]));return n?n.status!=="running"?(r(e,409,{error:`Sequence already ${n.status}`}),!0):(t.sequenceEngine?t.sequenceEngine.cancel(n.id):t.sessionSequences.finish(n.id,"cancelled"),r(e,200,{ok:!0,sequenceId:n.id}),!0):(r(e,404,{error:"Sequence not found"}),!0)}if(c==="/messaging/capabilities"&&s.method==="GET"){const n=t.messageDeliverer?.hasTerminalChannel()??!1;return r(e,200,{capabilities:ve().map(({harness:o,surface:i})=>({harness:o,discovery:i.discovery,prompt:T(i,n),inject:i.inject!==null,headless:i.headless!==null,watch:i.idleSignals.length>0,imageAttach:i.imageAttach!==null,sideQuestion:i.sideQuestion!==null,unsupportedReasons:i.unsupportedReasons??null}))}),!0}if(c==="/messaging/diagnostics"&&s.method==="GET"){if(!t.messageDeliverer||!t.hookSessions)return r(e,503,{error:"Message subsystem unavailable"}),!0;const n=f.searchParams.get("session"),o=f.searchParams.get("project"),i=Se(),u=t.projects.list(),m=await Promise.all(u.map(async l=>({id:l.id,name:l.name,path:l.path,captures:await Promise.all(i.map(async({harness:g,surface:p})=>({harness:g,...await p.scriptHealth({projectPath:l.path})})))}))),d=(l,g)=>m.find(p=>p.id===l)?.captures.find(p=>p.harness===g)??null,h=z(),b=t.hookSessions.listWithCounts(n?void 0:o?Number(o):void 0).filter(l=>n?l.sessionId===n:J(l,h)!==!1).map(l=>{const g=t.messageDeliverer.diagnose(l.sessionId),p=d(l.projectId,l.harness??null),I={inbox:g.inbox,relay:g.relay,terminal:g.terminal};return{sessionId:l.sessionId,projectId:l.projectId,harness:l.harness??null,title:l.title,lastSeenAt:l.lastSeenAt,eventCount:l.eventCount??0,lastEventType:l.lastEventType??null,alive:g.alive,paneEnvReported:g.paneEnvReported,channels:I,queue:{pending:g.pending,stalled:g.stalled},reachable:Object.values(I).some(v=>v.status==="ready"),hints:Je({diag:g,capture:p,eventCount:l.eventCount??0})}});return r(e,200,{daemon:{version:Te,platform:process.platform,dataDir:we(),uptimeMs:Math.round(process.uptime()*1e3),terminalPreset:be().terminal.preset,terminalTextChannel:t.messageDeliverer.hasTerminalChannel(),hooksEnabled:ye()},projects:m,sessions:b}),!0}return!1}
|
|
@@ -10,6 +10,7 @@ import { type MirrorWriter } from '../hooks/mirror.js';
|
|
|
10
10
|
import type { HookDispatcher } from '../hooks/dispatcher.js';
|
|
11
11
|
import type { HookEventBus } from '../hooks/bus.js';
|
|
12
12
|
import type { DataBus } from './data-bus.js';
|
|
13
|
+
import { SseHub } from './sse-hub.js';
|
|
13
14
|
import type { TerminalTopologyRefresher } from './terminal-topology-refresher.js';
|
|
14
15
|
import type { InflightCountsRefresher } from './inflight-counts-refresher.js';
|
|
15
16
|
import type { SnapshotRefresher } from './snapshot-refresher.js';
|
|
@@ -18,6 +19,7 @@ import type { ProjectUpdatesRefresher } from './project-updates-refresher.js';
|
|
|
18
19
|
import type { ResumeRunner } from '../hooks/resume-runner.js';
|
|
19
20
|
import type { MessageDeliverer } from '../hooks/message-deliverer.js';
|
|
20
21
|
import type { QuestionTracker } from '../hooks/question-tracker.js';
|
|
22
|
+
import type { SideQuestionDeps } from '../hooks/side-questions.js';
|
|
21
23
|
import type { WatchEngine } from '../hooks/watch-engine.js';
|
|
22
24
|
import type { SessionWatchRepository } from '../repositories/session-watches.js';
|
|
23
25
|
import type { PushSubscriptionRepository } from '../repositories/push-subscriptions.js';
|
|
@@ -72,6 +74,8 @@ export interface WeaveServerDeps {
|
|
|
72
74
|
hookBus?: HookEventBus;
|
|
73
75
|
/** 数据变更帧总线(SSE 第三订阅源:载荷推送 / 失效键)。 */
|
|
74
76
|
dataBus?: DataBus;
|
|
77
|
+
/** 事件流中枢(编号 + 环形缓冲 + 扇出):weaveRequestHandler 建一份,两监听共享。 */
|
|
78
|
+
sseHub?: SseHub;
|
|
75
79
|
/** 终端世界(角标计数 + 会话所在屏幕)的后端隐藏刷新器;缺省则该角标只能靠前端自拉(兼容旧装配)。 */
|
|
76
80
|
terminalRefresher?: TerminalTopologyRefresher;
|
|
77
81
|
/** 会话中断态快照刷新器(ESC 打断后多数连 Stop 都不发):中断成功后即时补一拍。 */
|
|
@@ -94,6 +98,9 @@ export interface WeaveServerDeps {
|
|
|
94
98
|
uploads?: UploadRepository;
|
|
95
99
|
/** 阻塞问答跟踪器(AskUserQuestion / 计划与权限确认);/messaging/questions 与 /sessions/:id/answer 依赖它。 */
|
|
96
100
|
questionTracker?: QuestionTracker;
|
|
101
|
+
/** 旁路会话历史(claude = /btw):问句只有 weave 自己发过才知道,故得自己记;
|
|
102
|
+
* `/sessions/:id/side-question(s)` 与删会话级联依赖它。 */
|
|
103
|
+
sideQuestions?: SideQuestionDeps;
|
|
97
104
|
/** 空闲订阅存储(批 2);POST/DELETE /sessions/:id/watch 与 GET /messaging/watches 依赖它。 */
|
|
98
105
|
sessionWatches?: SessionWatchRepository;
|
|
99
106
|
/** Web Push 订阅存储;POST /messaging/push/(un)subscribe 依赖它。 */
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import dt from"node:http";import{WEAVE_VERSION as pt}from"../version.js";import{checkHarness as gt,isHarness as M}from"@weavecode/core";import j from"node:fs";import S from"node:path";import{hooksRoot as yt}from"../hooks/mirror.js";import{syncPlugins as bt}from"../hooks/plugin-project-state.js";import{getLibraryScan as rt,invalidateScan as k}from"../install/scan-cache.js";import{installSkill as wt,uninstallSkill as kt,installMcp as jt,uninstallMcp as St,installFileAsset as vt,uninstallFileAsset as Pt,InstallConflictError as at,AssetNotFoundError as B}from"../install/installer.js";import{isFileAssetCategory as it}from"../install/file-assets.js";import{installCapabilities as It}from"../install/capabilities.js";import{readMcpRows as Nt}from"../install/mcp-rows.js";import{writeGlobalMcpServer as Et,removeGlobalMcpServer as Tt,installGlobalSkill as Ot,uninstallGlobalSkill as Mt,readGlobalSkillGroups as Bt,resolveGlobalSkillDir as ot,listGlobalSkillFiles as Ct,readGlobalSkillFile as Gt,ClaudeJsonCorruptError as Jt}from"../install/global-config.js";import{writeCodexMcpServer as At,removeCodexMcpServer as Dt,codexMcpExists as $t,CodexTomlCorruptError as Ft}from"../install/codex-toml.js";import{setMcpEnableOverride as xt}from"../install/settings-mcp.js";import{readProjectConfig as nt,writeProjectConfig as Lt}from"../install/project-config.js";import{applyUpdate as Rt,syncAll as Ut}from"../install/apply-update.js";import{buildLibraryIndex as Ht}from"../install/library-index.js";import{importFromDir as Kt,importFromGit as Wt,importLibraryIntoMarket as zt}from"../market/import.js";import{marketDir as C,readManifest as _t}from"../market/manifest.js";import{marketIdOf as Vt}from"../market/identity.js";import{isInsideDir as G,toRelPosix as Qt}from"../market/resolve.js";import{checkMarketUpdates as Xt}from"../market/updates.js";import{refreshMarketItems as Yt}from"../market/refresh.js";import{ensureBuiltinMarket as Zt,isBuiltinMarketDir as I}from"../market/builtin.js";import{removeInstalled as qt}from"../market/install-link.js";import{readProjectFile as te,readGlobalFile as ee,PathEscapeError as re,FileTooLargeError as ae}from"../install/reader.js";import{listGlobalFileAssets as ie}from"../install/global-file-assets.js";import{readStatuslineConfig as st,readStatuslineScript as oe,applyStatuslineConfig as ct,readGlobalStatuslineConfig as lt,writeGlobalStatuslineConfig as ne}from"../statusline/manager.js";import{DEFAULT_STATUSLINE_CONFIG as se}from"../statusline/config.js";import{FingerprintCache as J}from"../install/fingerprint.js";import{detectUpdates as ce}from"../install/updates.js";import{detectPluginTemplateUpdates as le}from"../install/plugin-templates.js";import{detectInitFileUpdates as ue,needsInit as me}from"../install/init-files.js";import{readBody as d,sendJson as i}from"./http.js";import{ApiLogger as fe}from"./api-log.js";import{handleHooksRoutes as he}from"./hooks-routes.js";import{handleToolkitRoutes as de}from"./toolkit-routes.js";import{handleCaptureRoutes as pe}from"./capture-routes.js";import{purgeProjectPipeline as ge}from"./hook-pipeline.js";import{projectFileNamespaces as ye}from"../hooks/toolkit.js";import{listBrowseRoots as be,listDirectoryChildren as we}from"./fs-browser.js";import{DEFAULT_AI_SETTINGS as ke,readDaemonSettings as v,writeDaemonSettings as je,terminalPresetsFor as Se}from"./settings.js";import{closeTerminalsIn as ve,defaultProjectOpener as P,readTerminalCounts as Pe,terminalBackendFor as Ie}from"./opener.js";import{applyLaunchOnLogin as Ne}from"./auto-start.js";import{DAEMON_HOST as Ee}from"./port.js";import{pluginStateSurfaces as Te,pluginSurfaces as ut,pluginSurfaceFor as mt}from"../surfaces.js";import{readPluginRows as Oe}from"../install/plugin-rows.js";import{installPluginPack as Me}from"../install/plugin-install.js";import{handleAuthRoutes as Be}from"./auth-routes.js";import{handleAiRoutes as Ce}from"./ai-routes.js";import{hasAiKey as Ge,writeAiKey as Je}from"./ai-config.js";import{accessUrls as Ae}from"./tls.js";import{projectHarnesses as De,withProjectHarnesses as ft}from"./project-harnesses.js";export const DAEMON_VERSION=pt;export function weaveRequestHandler(o){const t=o.fingerprintCache??new J,r=o.apiLog??new fe,u={...o,fingerprintCache:t,apiLog:r};return(s,g)=>{r.begin(s,g),$e(s,g,u)}}export function createWeaveServer(o){return dt.createServer(weaveRequestHandler(o))}async function $e(o,t,r){const u=new URL(o.url??"/",`http://${Ee}`);let s=u.pathname;if(s.startsWith("/api/")?s=s.slice(4):s==="/api"&&(s="/"),s==="/health"&&o.method==="GET"){i(t,200,{service:"weave-daemon",version:DAEMON_VERSION,pid:process.pid,port:r.port,uptimeMs:Math.round(process.uptime()*1e3)});return}if(await Be(o,t,r,s)||await Ce(o,t,r,s))return;if(r.auth&&!xe(o,s,r)&&!r.auth.isAuthenticated(o.headers)){i(t,401,{error:"Unauthorized"});return}if(s==="/api-logs"&&o.method==="GET"){if(!r.apiLog){i(t,503,{error:"API log unavailable"});return}const a=u.searchParams.get("status"),e=u.searchParams.get("limit"),n=await r.apiLog.query({from:u.searchParams.get("from")??void 0,to:u.searchParams.get("to")??void 0,path:u.searchParams.get("path")??void 0,method:u.searchParams.get("method")??void 0,status:a==null?void 0:Number(a),limit:e==null?void 0:Number(e)});i(t,200,n);return}if(await he(o,t,r,s,u)||await de(o,t,r,s,u)||await pe(o,t,r,s))return;const g=s.match(/^\/projects\/(\d+)\/hooks$/);if(g&&o.method==="GET"){if(!r.hookEvents){i(t,503,{error:"Hook ingestion unavailable"});return}const a=Number(g[1]),e=Ke(u.searchParams.get("limit")),n=u.searchParams.get("sessionId"),c=u.searchParams.get("beforeId"),l=c?Number(c):void 0,m=(u.searchParams.get("types")??"").split(",").map(p=>p.trim()).filter(Boolean);if(n){const p=r.hookEvents.listBySession(n,e??300,l,m),h=r.uploads?.imagesBySession(n);i(t,200,{events:p.map(f=>{const N=h?.get(f.id);return N?.length?{...f,images:N.map(O=>({id:O.id,name:O.name,mediaType:O.mediaType}))}:f})});return}i(t,200,{events:r.hookEvents.listByProject(a,e)});return}if(s==="/settings"){if(o.method==="GET"){i(t,200,{settings:ht(await v(),r.aiKeyPath),presets:Se(process.platform),platform:process.platform,urls:Ae(r.port,r.tlsPort??0)});return}if(o.method==="PUT"){let a;try{a=JSON.parse(await d(o))}catch{i(t,400,{error:"Invalid JSON body"});return}const e=a.ai?.apiKey;e!==void 0&&Je(typeof e=="string"?e:null,r.aiKeyPath);const n=await je({...a,ai:Ue(a.ai)});r.hookEvents?.resetPrune(),(r.autoStart??Ne)(n.startup?.launchOnLogin??!1),r.dataBus?.emit({key:"settings"}),i(t,200,ht(n,r.aiKeyPath));return}}const b=s.match(/^\/projects\/(\d+)\/open$/);if(b&&o.method==="POST"){const a=r.projects.getById(Number(b[1]));if(!a){i(t,404,{error:"Project not found"});return}let e;try{e=JSON.parse(await d(o))}catch{i(t,400,{error:"Invalid JSON body"});return}if(e.target!=="explorer"&&e.target!=="terminal"){i(t,400,{error:'Invalid "target" (explorer|terminal)'});return}const n=r.opener??P;try{if(!(await j.promises.stat(a.path)).isDirectory())throw new Error(`Not a directory: ${a.path}`);if(e.target==="explorer")await n.openExplorer(a.path);else{const l=await v();await n.openTerminal(a.path,l.terminal),setTimeout(()=>{r.terminalRefresher?.refreshNow()},600)}i(t,200,{ok:!0,target:e.target,path:a.path})}catch(c){i(t,400,{error:c instanceof Error?c.message:"Open failed"})}return}const y=s.match(/^\/projects\/(\d+)\/close-terminal$/);if(y&&o.method==="POST"){const a=r.projects.getById(Number(y[1]));if(!a){i(t,404,{error:"Project not found"});return}let e;try{e=JSON.parse(await d(o)||"{}")}catch{i(t,400,{error:"Invalid JSON body"});return}if(!Array.isArray(e.paneIds)||e.paneIds.length===0||e.paneIds.some(l=>typeof l!="string")){i(t,400,{error:"paneIds must be a non-empty array of strings"});return}const n=r.opener??P,c=await v();try{const m=await(n.closeTerminals??ve)(a.path,c.terminal,e.paneIds);setTimeout(()=>{r.terminalRefresher?.refreshNow()},600),i(t,200,{ok:!0,closed:m})}catch(l){i(t,400,{error:l instanceof Error?l.message:"Close failed"})}return}const w=s.match(/^\/projects\/(\d+)\/new-session$/);if(w&&o.method==="POST"){const a=r.projects.getById(Number(w[1]));if(!a){i(t,404,{error:"Project not found"});return}let e;try{e=JSON.parse(await d(o)||"{}")}catch{i(t,400,{error:"Invalid JSON body"});return}const n=await De(a.path,r.harnesses);if(e.harness!==void 0&&!M(e.harness)){i(t,400,{error:'Invalid "harness"'});return}const c=e.harness;if(c&&!n.some(f=>f.id===c)){i(t,409,{error:`Harness "${c}" is not integrated in this project`});return}const l=c?[c]:n.map(f=>f.id),m=l.map(f=>({id:f,spec:r.harnesses?.get(f)?.resume})).find(f=>f.spec?.bin);if(!m){i(t,501,{error:`Harness "${l[0]}" cannot start an interactive session`});return}const p=m.spec,h=r.opener??P;try{if(!(await j.promises.stat(a.path)).isDirectory())throw new Error(`Not a directory: ${a.path}`);const N=await v();await h.runInTerminal(a.path,[p.bin],N.terminal),setTimeout(()=>{r.terminalRefresher?.refreshNow()},600),i(t,200,{ok:!0,command:p.bin,cwd:a.path})}catch(f){i(t,400,{error:f instanceof Error?f.message:"Open failed"})}return}if(s==="/fs/roots"&&o.method==="GET"){const a=r.libraries?r.libraries.list().map(n=>n.path):[],e=await be(a);i(t,200,{roots:e});return}if(s==="/fs/children"&&o.method==="GET"){const a=u.searchParams.get("path");if(!a){i(t,400,{error:'Missing "path" query parameter'});return}try{const e=await we(a);i(t,200,{children:e})}catch(e){i(t,400,{error:`Cannot list directory: ${String(e)}`})}return}if(s==="/capabilities"&&o.method==="GET"){i(t,200,It());return}if(s==="/libraries"&&o.method==="GET"){i(t,200,{libraries:r.libraries?r.libraries.list():[]});return}if(s==="/libraries"&&o.method==="POST"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}let a;try{a=JSON.parse(await d(o))}catch{i(t,400,{error:"Invalid JSON body"});return}if(!a.path||!a.kind){i(t,400,{error:'Missing "path" or "kind"'});return}if(!["skill","mcp","both"].includes(a.kind)){i(t,400,{error:'Invalid "kind" (skill|mcp|both)'});return}const e=r.libraries.add({path:a.path,kind:a.kind});r.dataBus?.emit({key:"libraries"}),i(t,201,e);return}const D=s.match(/^\/libraries\/(\d+)\/assets$/);if(D&&o.method==="GET"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}const a=r.libraries.getById(Number(D[1]));if(!a){i(t,404,{error:"Library not found"});return}const{plugins:e,skills:n,mcp:c,files:l}=await rt(a.path),m=a.kind==="skill"||a.kind==="both"?e:[],p=a.kind==="mcp"||a.kind==="both"?c:[],h=a.kind==="skill"||a.kind==="both"?Object.values(l).flat():null;i(t,200,{library:a,skills:n,mcp:p,files:h,plugins:m});return}const $=s.match(/^\/libraries\/(\d+)$/);if($&&o.method==="DELETE"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}const a=Number($[1]),e=r.libraries.getById(a);e&&await k(e.path),r.libraries.remove(a),r.dataBus?.emit({key:"libraries"}),r.dataBus?.emit({key:"market"}),i(t,200,{ok:!0});return}if(s==="/market"&&o.method==="GET"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}const a=await Promise.all(r.libraries.list().map(e=>T(e,r.libraries.dataDir)));i(t,200,{items:a});return}if(s==="/market"&&o.method==="POST"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}let a;try{a=JSON.parse(await d(o))}catch{i(t,400,{error:"Invalid JSON body"});return}const e={libraries:r.libraries,dataDir:r.libraries.dataDir};try{if(a.kind==="dir"&&a.path){const n=await Kt(a.path,e);n.refreshed&&r.fingerprintCache?.invalidate(n.dir),await k(n.dir),r.dataBus?.emit({key:"market"}),i(t,201,{item:await T(r.libraries.getById(n.libraryId),r.libraries.dataDir)})}else if(a.kind==="git"&&a.url){const n=await Wt(a.url,e);n.refreshed&&r.fingerprintCache?.invalidate(n.dir),await k(n.dir),r.dataBus?.emit({key:"market"}),i(t,201,{item:await T(r.libraries.getById(n.libraryId),r.libraries.dataDir)})}else i(t,400,{error:'Invalid body (kind: "dir" + path | "git" + url)'})}catch(n){i(t,400,{error:n instanceof Error?n.message:"Import failed"})}return}if(s==="/market/check-updates"&&o.method==="POST"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}let a={};const e=(await d(o)).trim();if(e)try{a=JSON.parse(e)}catch{i(t,400,{error:"Invalid JSON body"});return}const n=r.libraries.list().filter(l=>!I(l.path,r.libraries.dataDir)).filter(l=>!a.ids?.length||a.ids.includes(l.id));for(const l of n)await k(l.path);const c=await Xt(n.map(l=>({id:l.id,dir:l.path})));r.dataBus?.emit({key:"market"}),i(t,200,{results:c});return}if(s==="/market/refresh"&&o.method==="POST"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}let a={};const e=(await d(o)).trim();if(e)try{a=JSON.parse(e)}catch{i(t,400,{error:"Invalid JSON body"});return}const n=r.libraries.list().filter(h=>!a.ids?.length||a.ids.includes(h.id)),c=[];for(const h of n.filter(f=>I(f.path,r.libraries.dataDir))){const f=await Zt({libraries:r.libraries});c.push({id:h.id,status:f.error?"failed":"refreshed"})}const l=n.filter(h=>!I(h.path,r.libraries.dataDir)).map(h=>({id:h.id,dir:h.path})),m=[...await Yt(l,{libraries:r.libraries,dataDir:r.libraries.dataDir}),...c],p=new Map(l.map(h=>[h.id,h.dir]));for(const h of m)if(h.status==="refreshed"){const f=p.get(h.id);f&&(r.fingerprintCache?.invalidate(f),await k(f))}r.dataBus?.emit({key:"market"}),i(t,200,{results:m});return}const F=s.match(/^\/market\/(\d+)$/);if(F&&o.method==="DELETE"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}const a=r.libraries.getById(Number(F[1]));if(!a){i(t,404,{error:"Library not found"});return}if(I(a.path,r.libraries.dataDir)){i(t,409,{error:"Built-in market is owned by weave and cannot be deleted",note:"It is re-materialized from @weavecode/templates on every daemon start."});return}if(!G(a.path,C(r.libraries.dataDir))){i(t,400,{error:"Not a market resource (legacy row \u2014 use DELETE /libraries/:id)"});return}let e;try{await(r.pluginSurface??mt("claude")).removeMarketplace(a.path)}catch(n){e=`Host marketplace deregistration failed: ${n instanceof Error?n.message:String(n)}`}await qt(a.path),r.libraries.remove(a.id),await k(a.path),r.dataBus?.emit({key:"market"}),r.dataBus?.emit({key:"libraries"}),i(t,200,{ok:!0,removed:a.path,installedCopiesKept:!0,...e?{warning:e}:{},note:"The host cascade-uninstalls plugins installed from this market; other installed copies are kept."});return}const x=s.match(/^\/libraries\/(\d+)\/market-import$/);if(x&&o.method==="POST"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}const a=r.libraries.getById(Number(x[1]));if(!a){i(t,404,{error:"Library not found"});return}try{const e=a.path,n=await zt(a,{libraries:r.libraries,dataDir:r.libraries.dataDir});await k(e),await k(n.dir),i(t,200,{item:await T(r.libraries.getById(n.libraryId),r.libraries.dataDir)})}catch(e){i(t,400,{error:e instanceof Error?e.message:"Import failed"})}return}const L=s.match(/^\/projects\/(\d+)\/install$/);if(L&&o.method==="POST"){const a=r.projects.getById(Number(L[1]));if(!a){i(t,404,{error:"Project not found"});return}let e;try{e=JSON.parse(await d(o))}catch{i(t,400,{error:"Invalid JSON body"});return}try{if(e.category==="skill"){if(!e.name||!e.sourceDir){i(t,400,{error:'Missing "name" or "sourceDir"'});return}const n=e.agent??"claude",c=e.scope==="user"?await Ot(e.sourceDir,e.name,n):await wt(a.path,e.sourceDir,e.name,n);i(t,201,{category:"skill",name:e.name,scope:e.scope??"project",agent:n,path:c})}else if(e.category&&it(e.category)){if(!e.name||!e.sourceFile){i(t,400,{error:'Missing "name" or "sourceFile"'});return}const n=e.agent??"claude",c=e.scope??"project",l=await vt(a.path,e.category,e.name,e.sourceFile,n,c);i(t,201,{category:e.category,name:e.name,scope:c,agent:n,path:l})}else if(e.category==="mcp"){if(!e.name||!e.mcpConfig){i(t,400,{error:'Missing "name" or "mcpConfig"'});return}if(e.agent==="codex"){if(e.scope==="project"){i(t,400,{error:'Codex MCP servers are user-level (~/.codex/config.toml) \u2014 use scope "user"'});return}if(await $t(e.name)){E(t,new at(`MCP server "${e.name}" is already configured in ~/.codex/config.toml`));return}await At(e.name,e.mcpConfig)}else e.scope==="user"?await Et(e.name,e.mcpConfig):await jt(a.path,e.name,e.mcpConfig);i(t,201,{category:"mcp",name:e.name,scope:e.agent==="codex"?"user":e.scope??"project",agent:e.agent??"claude"})}else if(e.category==="plugin"){if(!e.name||!e.sourceDir){i(t,400,{error:'Missing "name" or "sourceDir"'});return}if(e.scope&&e.scope!=="user"){i(t,400,{error:'Plugin install currently only supports scope "user"'});return}const n=e.agent??"claude",c=ut().find(p=>p.harness===n);if(!c){i(t,400,{error:`No plugin install surface for runtime: ${n}`});return}const l=n==="claude"&&r.pluginSurface?r.pluginSurface:c.surface,m=await Me(l,{dir:e.sourceDir,name:e.name,version:e.version,description:e.description});r.dataBus?.emit({key:"toolkit"}),i(t,201,{category:"plugin",key:m.key,scope:"user",agent:n,alreadyInstalled:m.alreadyInstalled})}else i(t,400,{error:'Invalid "category" (skill|mcp|plugin|command|agent|workflow|rule|output-style)'})}catch(n){E(t,n)}return}const R=s.match(/^\/projects\/(\d+)\/uninstall$/);if(R&&o.method==="POST"){const a=r.projects.getById(Number(R[1]));if(!a){i(t,404,{error:"Project not found"});return}let e;try{e=JSON.parse(await d(o))}catch{i(t,400,{error:"Invalid JSON body"});return}try{if(e.category==="skill"){if(!e.name){i(t,400,{error:'Missing "name"'});return}const n=e.agent??"claude";e.scope==="user"?await Mt(e.name,n):await kt(a.path,e.name,n)}else if(e.category&&it(e.category)){if(!e.name){i(t,400,{error:'Missing "name"'});return}await Pt(a.path,e.category,e.name,e.agent??"claude",e.scope??"project")}else if(e.category==="mcp"){if(!e.name){i(t,400,{error:'Missing "name"'});return}if(e.agent==="codex"){if(!await Dt(e.name)){E(t,new B(`MCP server "${e.name}" is not configured in ~/.codex/config.toml`));return}}else e.scope==="user"?await Tt(e.name):await St(a.path,e.name)}else{i(t,400,{error:'Invalid "category" (skill|mcp|command|agent|workflow|rule|output-style)'});return}i(t,200,{ok:!0})}catch(n){E(t,n)}return}const U=s.match(/^\/projects\/(\d+)\/file$/);if(U&&o.method==="GET"){const a=r.projects.getById(Number(U[1]));if(!a){i(t,404,{error:"Project not found"});return}const e=u.searchParams.get("path");if(!e){i(t,400,{error:'Missing "path" query parameter'});return}try{const n=await te(a.path,e);i(t,200,{path:e,content:n.content,size:n.size})}catch(n){A(t,n)}return}if(s==="/global-file"&&o.method==="GET"){const a=u.searchParams.get("path");if(!a){i(t,400,{error:'Missing "path" query parameter'});return}try{const e=await ee(a);i(t,200,{path:a,content:e.content,size:e.size})}catch(e){A(t,e)}return}const H=s.match(/^\/projects\/(\d+)\/mcp$/);if(H&&o.method==="GET"){const a=r.projects.getById(Number(H[1]));if(!a){i(t,404,{error:"Project not found"});return}i(t,200,{rows:await Nt(a.path)});return}const K=s.match(/^\/projects\/(\d+)\/harness-checks$/);if(K&&o.method==="GET"){const a=r.projects.getById(Number(K[1]));if(!a){i(t,404,{error:"Project not found"});return}i(t,200,{checks:await gt(a.path)});return}if(s.match(/^\/projects\/(\d+)\/skills\/global$/)&&o.method==="GET"){i(t,200,{groups:await Bt()});return}if(s.match(/^\/projects\/(\d+)\/skills\/global\/files$/)&&o.method==="GET"){const a=u.searchParams.get("source"),e=u.searchParams.get("name"),n=u.searchParams.get("pluginKey")??void 0;if(!a||!e){i(t,400,{error:'Missing "source" or "name" query parameter'});return}const c=await ot(a,e,n);if(!c){i(t,404,{error:"Skill not found"});return}i(t,200,{files:await Ct(c)});return}if(s.match(/^\/projects\/(\d+)\/skills\/global\/file$/)&&o.method==="GET"){const a=u.searchParams.get("source"),e=u.searchParams.get("name"),n=u.searchParams.get("pluginKey")??void 0,c=u.searchParams.get("path");if(!a||!e||!c){i(t,400,{error:'Missing "source", "name" or "path" query parameter'});return}const l=await ot(a,e,n);if(!l){i(t,404,{error:"Skill not found"});return}try{const m=await Gt(l,c);i(t,200,{path:c,content:m.content,size:m.size})}catch(m){A(t,m)}return}const W=s.match(/^\/projects\/(\d+)\/plugins\/state$/);if(W){const a=r.projects.getById(Number(W[1]));if(!a){i(t,404,{error:"Project not found"});return}if(o.method==="GET"){i(t,200,{rows:await Oe(a.path)});return}if(o.method==="PUT"){let e;try{e=JSON.parse(await d(o))}catch{i(t,400,{error:"Invalid JSON body"});return}if(!M(e.runtime)||!e.key||!e.scope||!["user","project","local"].includes(e.scope)||!e.state||!["on","off","inherit"].includes(e.state)){i(t,400,{error:'Missing or invalid "runtime"/"key"/"scope" (user|project|local)/"state" (on|off|inherit)'});return}const n=e.scope,c=e.state;try{const l=ut().find(m=>m.harness===e.runtime);if(l)await(e.runtime==="claude"&&r.pluginSurface?r.pluginSurface:l.surface).setEnabledOverride(a.path,n,e.key,c);else{const m=Te().find(p=>p.harness===e.runtime);if(!m||n!=="user"||c==="inherit"){i(t,400,{error:"This runtime only supports user-scope on/off overrides"});return}await m.state.writeGlobalEnabled(e.key,c==="on")}r.dataBus?.emit({key:"toolkit"}),i(t,200,{ok:!0})}catch(l){i(t,400,{error:l instanceof Error?l.message:"Plugin state write failed"})}return}}if(s.match(/^\/projects\/(\d+)\/plugins\/uninstall$/)&&o.method==="POST"){let a;try{a=JSON.parse(await d(o))}catch{i(t,400,{error:"Invalid JSON body"});return}if(!M(a.runtime)||!a.key){i(t,400,{error:'Missing or invalid "runtime"/"key"'});return}try{const e=a.runtime==="claude"&&r.pluginSurface?r.pluginSurface:mt(a.runtime),{name:n,market:c}=e.parsePluginKey(a.key);await e.uninstall(n,c),i(t,200,{ok:!0})}catch(e){i(t,400,{error:e instanceof Error?e.message:"Plugin uninstall failed"})}return}const z=s.match(/^\/projects\/(\d+)\/mcp\/enable$/);if(z){const a=r.projects.getById(Number(z[1]));if(!a){i(t,404,{error:"Project not found"});return}if(o.method==="PUT"){let e;try{e=JSON.parse(await d(o))}catch{i(t,400,{error:"Invalid JSON body"});return}if(!e.name||!e.state){i(t,400,{error:'Missing "name" or "state"'});return}if(!["on","off","inherit"].includes(e.state)){i(t,400,{error:'Invalid "state" (on|off|inherit)'});return}await xt(a.path,e.name,e.state),i(t,200,{ok:!0});return}}const _=s.match(/^\/projects\/(\d+)\/updates$/);if(_&&o.method==="GET"){const a=r.projects.getById(Number(_[1]));if(!a){i(t,404,{error:"Project not found"});return}const e=r.fingerprintCache??new J;if(!r.libraries){const c=await le(a.path,e),l=await ue(a.path);i(t,200,{skills:[],mcp:[],files:[],pluginTemplates:c,initFiles:l,available:c.filter(m=>m.outdated).length+l.filter(m=>m.outdated&&m.syncClass==="auto").length,needsInit:me(l)});return}const n=await ce(a.path,r.libraries.list(),e);i(t,200,n);return}const V=s.match(/^\/projects\/(\d+)\/update$/);if(V&&o.method==="POST"){const a=r.projects.getById(Number(V[1]));if(!a){i(t,404,{error:"Project not found"});return}if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}let e;try{e=JSON.parse(await d(o))}catch{i(t,400,{error:"Invalid JSON body"});return}if(!e.category||!e.name||!e.scope){i(t,400,{error:'Missing "category", "name", or "scope"'});return}if(!["skill","mcp","command","agent","workflow","rule","output-style"].includes(e.category)){i(t,400,{error:'Invalid "category"'});return}try{const c=await Ht(r.libraries.list());await Rt(a.path,{category:e.category,name:e.name,scope:e.scope,agent:e.agent},c),i(t,200,{ok:!0,category:e.category,name:e.name,scope:e.scope})}catch(c){i(t,400,{error:c instanceof Error?c.message:"Update failed"})}return}const Q=s.match(/^\/projects\/(\d+)\/sync-all$/);if(Q&&o.method==="POST"){const a=r.projects.getById(Number(Q[1]));if(!a){i(t,404,{error:"Project not found"});return}if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}try{const e=r.fingerprintCache??new J,n=await Ut(a.path,r.libraries.list(),e);let c=n;try{const l=r.hookPlugins?.list().map(p=>p.id)??[],m=bt(a,l,{projects:r.projects});c={...n,pluginSync:{syncedAt:m}}}catch{}i(t,200,c),r.updatesRefresher?.refreshNow(!0)}catch(e){i(t,400,{error:e instanceof Error?e.message:"Sync failed"})}return}const X=s.match(/^\/projects\/(\d+)\/config$/);if(X){const a=r.projects.getById(Number(X[1]));if(!a){i(t,404,{error:"Project not found"});return}if(o.method==="GET"){i(t,200,await nt(a.path));return}if(o.method==="PUT"){let e;try{e=JSON.parse(await d(o))}catch{i(t,400,{error:"Invalid JSON body"});return}const n=await nt(a.path),c={lastSyncAt:e.lastSyncAt??n.lastSyncAt};await Lt(a.path,c),i(t,200,c);return}}if(s==="/statusline/global"){if(o.method==="GET"){i(t,200,{config:await lt()});return}if(o.method==="PUT"){let a;try{a=JSON.parse(await d(o))}catch{i(t,400,{error:"Invalid JSON body"});return}try{await ne(a);for(const e of r.projects.list()){const n=await st(e.path);n.source==="global"&&await ct(e.path,n)}r.dataBus?.emit({key:"statusline"}),i(t,200,{ok:!0})}catch(e){i(t,400,{error:e instanceof Error?e.message:"Global statusline update failed"})}return}}if(s==="/statusline/default"&&o.method==="GET"){i(t,200,{config:se});return}const Y=s.match(/^\/projects\/(\d+)\/statusline$/);if(Y){const a=r.projects.getById(Number(Y[1]));if(!a){i(t,404,{error:"Project not found"});return}if(o.method==="GET"){const[e,n,c]=await Promise.all([st(a.path),oe(a.path),lt()]);i(t,200,{config:e,script:n,globalConfig:c});return}if(o.method==="PUT"){let e;try{e=JSON.parse(await d(o))}catch{i(t,400,{error:"Invalid JSON body"});return}try{await ct(a.path,e),r.dataBus?.emit({key:"statusline",projectPath:a.path}),i(t,200,{ok:!0})}catch(n){i(t,400,{error:n instanceof Error?n.message:"Statusline update failed"})}return}}if(s==="/events"&&o.method==="GET"){Fe(o,t,r);return}if(s==="/assets"&&o.method==="GET"){const a=u.searchParams.get("path");if(!a){i(t,400,{error:'Missing "path" query parameter'});return}if(!r.watch){i(t,503,{error:"Watch service unavailable"});return}const e=await ye(a),n=r.watch.getAssets(a).map(c=>{const l=e.get(c.relPath);return l?{...c,namespace:l}:c});i(t,200,{projectPath:a,assets:n});return}if(s==="/global-file-assets"&&o.method==="GET"){i(t,200,{assets:await ie()});return}if(s==="/projects"&&o.method==="GET"){i(t,200,{projects:await ft(r.projects.list(),r.harnesses)});return}if(s==="/projects/archived"&&o.method==="GET"){i(t,200,{projects:await ft(r.projects.listArchived(),r.harnesses)});return}const Z=s.match(/^\/projects\/(\d+)\/archive$/);if(Z&&o.method==="POST"){const a=r.projects.getById(Number(Z[1]));if(!a){i(t,404,{error:"Project not found"});return}if(a.archivedAt){i(t,200,{project:a,purged:{events:0,dispatches:0,sessions:0,messages:0}});return}const e=r.hookDispatches?.deleteByProject(a.id)??0,n=r.hookEvents?.deleteByProject(a.id)??0;r.uploads?.pruneUnreferenced();const c=r.messageDeliverer?.forgetProject(a.id)??0;r.sessionWatches?.deleteByProject(a.id),r.sessionSequences?.deleteByProject(a.id);const l=r.hookSessions?.removeByProject(a.id)??0;r.projects.setArchived(a.id,!0),ge(r.hooksDir??yt(),a.token),r.watch?.reconcile(),r.dataBus?.emit({key:"projects"}),i(t,200,{project:r.projects.getById(a.id),purged:{events:n,dispatches:e,sessions:l,messages:c}});return}const q=s.match(/^\/projects\/(\d+)\/restore$/);if(q&&o.method==="POST"){const a=r.projects.getById(Number(q[1]));if(!a){i(t,404,{error:"Project not found"});return}if(!a.archivedAt){i(t,200,{project:a});return}r.projects.setArchived(a.id,!1),r.hookMirror?.syncProject(a.id),r.watch?.reconcile(),r.dataBus?.emit({key:"projects"}),i(t,200,{project:r.projects.getById(a.id)});return}if(s==="/projects/order"&&o.method==="PUT"){let a;try{a=JSON.parse(await d(o))}catch{i(t,400,{error:"Invalid JSON body"});return}if(!Array.isArray(a.ids)||!a.ids.every(e=>Number.isInteger(e))){i(t,400,{error:'Invalid "ids" (integer array expected)'});return}r.projects.setOrder(a.ids),r.dataBus?.emit({key:"projects"}),i(t,200,{projects:r.projects.list()});return}const tt=s.match(/^\/projects\/(\d+)\/panes$/);if(tt&&o.method==="GET"){const a=r.projects.getById(Number(tt[1]));if(!a){i(t,404,{error:"Project not found"});return}const e=await v(),n=r.opener?.listPanes??P.listPanes;try{i(t,200,{projectId:a.id,panes:await n(a.path,e.terminal)})}catch(c){i(t,409,{error:c instanceof Error?c.message:"Cannot list panes"})}return}const et=s.match(/^\/projects\/(\d+)\/terminals$/);if(et&&o.method==="GET"){const a=r.projects.getById(Number(et[1]));if(!a){i(t,404,{error:"Project not found"});return}const e=await v(),n=r.opener?.listTerminals??P.listTerminals,c=Ie(e.terminal).enumerable?await n(a.path,e.terminal):[];i(t,200,{projectId:a.id,terminals:c});return}if(s==="/terminals/counts"&&o.method==="GET"){if(r.terminalRefresher)i(t,200,{counts:r.terminalRefresher.lastSnapshot?.counts??{}}),r.terminalRefresher.tick(!0);else{const a=r.opener??P;i(t,200,{counts:await Pe(r.projects.list(),a)})}return}if(o.method==="GET"&&r.staticDir){Re(t,s,r.staticDir);return}i(t,404,{error:"Not found"})}function Fe(o,t,r){if(!r.watch&&!r.hookBus&&!r.dataBus){t.statusCode=503,t.setHeader("content-type","application/json"),t.end(JSON.stringify({error:"Event stream unavailable"}));return}t.writeHead(200,{"content-type":"text/event-stream","cache-control":"no-cache",connection:"keep-alive","x-accel-buffering":"no"}),t.write(`: connected
|
|
1
|
+
import yt from"node:http";import{WEAVE_VERSION as bt}from"../version.js";import{checkHarness as wt,isHarness as C}from"@weavecode/core";import S from"node:fs";import{homedir as $}from"node:os";import v from"node:path";import{hooksRoot as jt}from"../hooks/mirror.js";import{SseHub as kt}from"./sse-hub.js";import{syncPlugins as St}from"../hooks/plugin-project-state.js";import{getLibraryScan as it,invalidateScan as k}from"../install/scan-cache.js";import{installSkill as vt,uninstallSkill as Pt,installFileAsset as It,uninstallFileAsset as Et,InstallConflictError as ot,AssetNotFoundError as G}from"../install/installer.js";import{isFileAssetCategory as nt}from"../install/file-assets.js";import{installCapabilities as Nt}from"../install/capabilities.js";import{readMcpRows as Tt}from"../install/mcp-rows.js";import{installGlobalSkill as Ot,uninstallGlobalSkill as Mt,readGlobalSkillGroups as Bt,resolveGlobalSkillDir as st,listGlobalSkillFiles as Ct,readGlobalSkillFile as $t,ClaudeJsonCorruptError as Gt}from"../install/global-config.js";import{CodexTomlCorruptError as At}from"../install/codex-toml.js";import{forgetInstalled as Jt,recordInstalled as Dt}from"../install/install-ledger.js";import{setMcpEnableOverride as Ft}from"../install/settings-mcp.js";import{readProjectConfig as ct,writeProjectConfig as Rt}from"../install/project-config.js";import{applyUpdate as Lt,syncAll as Ht}from"../install/apply-update.js";import{buildLibraryIndex as Ut}from"../install/library-index.js";import{importFromDir as xt,importFromGit as Kt,importLibraryIntoMarket as Wt}from"../market/import.js";import{marketDir as A,readManifest as _t}from"../market/manifest.js";import{marketIdOf as zt}from"../market/identity.js";import{isInsideDir as J,toRelPosix as Vt}from"../market/resolve.js";import{checkMarketUpdates as Qt}from"../market/updates.js";import{refreshMarketItems as Xt}from"../market/refresh.js";import{ensureBuiltinMarket as Yt,isBuiltinMarketDir as E}from"../market/builtin.js";import{removeInstalled as Zt}from"../market/install-link.js";import{readProjectFile as qt,readGlobalFile as te,PathEscapeError as ee,FileTooLargeError as re}from"../install/reader.js";import{listGlobalFileAssets as ae}from"../install/global-file-assets.js";import{readStatuslineConfig as lt,readStatuslineScript as ie,readStatuslineFacts as oe,applyStatuslineConfig as ut,readGlobalStatuslineConfig as mt,writeGlobalStatuslineConfig as ne}from"../statusline/manager.js";import{DEFAULT_STATUSLINE_CONFIG as se}from"../statusline/config.js";import{FingerprintCache as D}from"../install/fingerprint.js";import{detectUpdates as ce}from"../install/updates.js";import{detectPluginTemplateUpdates as le}from"../install/plugin-templates.js";import{detectInitFileUpdates as ue,needsInit as me}from"../install/init-files.js";import{readBody as p,sendJson as i}from"./http.js";import{ApiLogger as fe}from"./api-log.js";import{handleHooksRoutes as de}from"./hooks-routes.js";import{handleToolkitRoutes as he}from"./toolkit-routes.js";import{handleCaptureRoutes as pe}from"./capture-routes.js";import{purgeProjectPipeline as ge}from"./hook-pipeline.js";import{projectFileNamespaces as ye}from"../hooks/toolkit.js";import{listBrowseRoots as be,listDirectoryChildren as we}from"./fs-browser.js";import{DEFAULT_AI_SETTINGS as je,readDaemonSettings as P,writeDaemonSettings as ke,terminalPresetsFor as Se}from"./settings.js";import{closeTerminalsIn as ve,defaultProjectOpener as I,readTerminalCounts as Pe,terminalBackendFor as Ie}from"./opener.js";import{applyLaunchOnLogin as Ee}from"./auto-start.js";import{DAEMON_HOST as Ne}from"./port.js";import{assetSurfaces as Te,pluginStateSurfaces as Oe,pluginSurfaces as ft,pluginSurfaceFor as dt}from"../surfaces.js";import{readPluginRows as Me}from"../install/plugin-rows.js";import{installPluginPack as Be}from"../install/plugin-install.js";import{handleAuthRoutes as Ce}from"./auth-routes.js";import{handleAiRoutes as $e}from"./ai-routes.js";import{hasAiKey as Ge,writeAiKey as Ae}from"./ai-config.js";import{accessUrls as Je}from"./tls.js";import{projectHarnesses as De,withProjectHarnesses as ht}from"./project-harnesses.js";export const DAEMON_VERSION=bt;export function weaveRequestHandler(o){const t=o.fingerprintCache??new D,r=o.apiLog??new fe,u=o.sseHub??new kt(o),c={...o,fingerprintCache:t,apiLog:r,sseHub:u};return(b,g)=>{r.begin(b,g),Fe(b,g,c)}}export function createWeaveServer(o){return yt.createServer(weaveRequestHandler(o))}async function Fe(o,t,r){const u=new URL(o.url??"/",`http://${Ne}`);let c=u.pathname;if(c.startsWith("/api/")?c=c.slice(4):c==="/api"&&(c="/"),c==="/health"&&o.method==="GET"){i(t,200,{service:"weave-daemon",version:DAEMON_VERSION,pid:process.pid,port:r.port,uptimeMs:Math.round(process.uptime()*1e3)});return}if(await Ce(o,t,r,c)||await $e(o,t,r,c))return;if(r.auth&&!He(o,c,r)&&!r.auth.isAuthenticated(o.headers)){i(t,401,{error:"Unauthorized"});return}if(c==="/api-logs"&&o.method==="GET"){if(!r.apiLog){i(t,503,{error:"API log unavailable"});return}const a=u.searchParams.get("status"),e=u.searchParams.get("limit"),n=await r.apiLog.query({from:u.searchParams.get("from")??void 0,to:u.searchParams.get("to")??void 0,path:u.searchParams.get("path")??void 0,method:u.searchParams.get("method")??void 0,status:a==null?void 0:Number(a),limit:e==null?void 0:Number(e)});i(t,200,n);return}if(await de(o,t,r,c,u)||await he(o,t,r,c,u)||await pe(o,t,r,c))return;const b=c.match(/^\/projects\/(\d+)\/hooks$/);if(b&&o.method==="GET"){if(!r.hookEvents){i(t,503,{error:"Hook ingestion unavailable"});return}const a=Number(b[1]),e=_e(u.searchParams.get("limit")),n=u.searchParams.get("sessionId"),s=u.searchParams.get("beforeId"),l=s?Number(s):void 0,m=(u.searchParams.get("types")??"").split(",").map(f=>f.trim()).filter(Boolean);if(n){const f=r.hookEvents.listBySession(n,e??300,l,m),h=r.uploads?.imagesBySession(n);i(t,200,{events:f.map(d=>{const T=h?.get(d.id);return T?.length?{...d,images:T.map(B=>({id:B.id,name:B.name,mediaType:B.mediaType}))}:d})});return}i(t,200,{events:r.hookEvents.listByProject(a,e)});return}if(c==="/settings"){if(o.method==="GET"){i(t,200,{settings:pt(await P(),r.aiKeyPath),presets:Se(process.platform),platform:process.platform,urls:Je(r.port,r.tlsPort??0)});return}if(o.method==="PUT"){let a;try{a=JSON.parse(await p(o))}catch{i(t,400,{error:"Invalid JSON body"});return}const e=a.ai?.apiKey;e!==void 0&&Ae(typeof e=="string"?e:null,r.aiKeyPath);const n=await ke({...a,ai:Ke(a.ai)});r.hookEvents?.resetPrune(),(r.autoStart??Ee)(n.startup?.launchOnLogin??!1),r.dataBus?.emit({key:"settings"}),i(t,200,pt(n,r.aiKeyPath));return}}const g=c.match(/^\/projects\/(\d+)\/open$/);if(g&&o.method==="POST"){const a=r.projects.getById(Number(g[1]));if(!a){i(t,404,{error:"Project not found"});return}let e;try{e=JSON.parse(await p(o))}catch{i(t,400,{error:"Invalid JSON body"});return}if(e.target!=="explorer"&&e.target!=="terminal"){i(t,400,{error:'Invalid "target" (explorer|terminal)'});return}const n=r.opener??I;try{if(!(await S.promises.stat(a.path)).isDirectory())throw new Error(`Not a directory: ${a.path}`);if(e.target==="explorer")await n.openExplorer(a.path);else{const l=await P();await n.openTerminal(a.path,l.terminal),setTimeout(()=>{r.terminalRefresher?.refreshNow()},600)}i(t,200,{ok:!0,target:e.target,path:a.path})}catch(s){i(t,400,{error:s instanceof Error?s.message:"Open failed"})}return}const y=c.match(/^\/projects\/(\d+)\/close-terminal$/);if(y&&o.method==="POST"){const a=r.projects.getById(Number(y[1]));if(!a){i(t,404,{error:"Project not found"});return}let e;try{e=JSON.parse(await p(o)||"{}")}catch{i(t,400,{error:"Invalid JSON body"});return}if(!Array.isArray(e.paneIds)||e.paneIds.length===0||e.paneIds.some(l=>typeof l!="string")){i(t,400,{error:"paneIds must be a non-empty array of strings"});return}const n=r.opener??I,s=await P();try{const m=await(n.closeTerminals??ve)(a.path,s.terminal,e.paneIds);setTimeout(()=>{r.terminalRefresher?.refreshNow()},600),i(t,200,{ok:!0,closed:m})}catch(l){i(t,400,{error:l instanceof Error?l.message:"Close failed"})}return}const w=c.match(/^\/projects\/(\d+)\/new-session$/);if(w&&o.method==="POST"){const a=r.projects.getById(Number(w[1]));if(!a){i(t,404,{error:"Project not found"});return}let e;try{e=JSON.parse(await p(o)||"{}")}catch{i(t,400,{error:"Invalid JSON body"});return}const n=await De(a.path,r.harnesses);if(e.harness!==void 0&&!C(e.harness)){i(t,400,{error:'Invalid "harness"'});return}const s=e.harness;if(s&&!n.some(d=>d.id===s)){i(t,409,{error:`Harness "${s}" is not integrated in this project`});return}const l=s?[s]:n.map(d=>d.id),m=l.map(d=>({id:d,spec:r.harnesses?.get(d)?.resume})).find(d=>d.spec?.bin);if(!m){i(t,501,{error:`Harness "${l[0]}" cannot start an interactive session`});return}const f=m.spec,h=r.opener??I;try{if(!(await S.promises.stat(a.path)).isDirectory())throw new Error(`Not a directory: ${a.path}`);const T=await P();await h.runInTerminal(a.path,[f.bin],T.terminal),setTimeout(()=>{r.terminalRefresher?.refreshNow()},600),i(t,200,{ok:!0,command:f.bin,cwd:a.path})}catch(d){i(t,400,{error:d instanceof Error?d.message:"Open failed"})}return}if(c==="/fs/roots"&&o.method==="GET"){const a=r.libraries?r.libraries.list().map(n=>n.path):[],e=await be(a);i(t,200,{roots:e});return}if(c==="/fs/children"&&o.method==="GET"){const a=u.searchParams.get("path");if(!a){i(t,400,{error:'Missing "path" query parameter'});return}try{const e=await we(a);i(t,200,{children:e})}catch(e){i(t,400,{error:`Cannot list directory: ${String(e)}`})}return}if(c==="/capabilities"&&o.method==="GET"){i(t,200,Nt());return}if(c==="/libraries"&&o.method==="GET"){i(t,200,{libraries:r.libraries?r.libraries.list():[]});return}if(c==="/libraries"&&o.method==="POST"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}let a;try{a=JSON.parse(await p(o))}catch{i(t,400,{error:"Invalid JSON body"});return}if(!a.path||!a.kind){i(t,400,{error:'Missing "path" or "kind"'});return}if(!["skill","mcp","both"].includes(a.kind)){i(t,400,{error:'Invalid "kind" (skill|mcp|both)'});return}const e=r.libraries.add({path:a.path,kind:a.kind});r.dataBus?.emit({key:"libraries"}),i(t,201,e);return}const N=c.match(/^\/libraries\/(\d+)\/assets$/);if(N&&o.method==="GET"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}const a=r.libraries.getById(Number(N[1]));if(!a){i(t,404,{error:"Library not found"});return}const{plugins:e,skills:n,mcp:s,files:l}=await it(a.path),m=a.kind==="skill"||a.kind==="both"?e:[],f=a.kind==="mcp"||a.kind==="both"?s:[],h=a.kind==="skill"||a.kind==="both"?Object.values(l).flat():null;i(t,200,{library:a,skills:n,mcp:f,files:h,plugins:m});return}const j=c.match(/^\/libraries\/(\d+)$/);if(j&&o.method==="DELETE"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}const a=Number(j[1]),e=r.libraries.getById(a);e&&await k(e.path),r.libraries.remove(a),r.dataBus?.emit({key:"libraries"}),r.dataBus?.emit({key:"market"}),i(t,200,{ok:!0});return}if(c==="/market"&&o.method==="GET"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}const a=await Promise.all(r.libraries.list().map(e=>M(e,r.libraries.dataDir)));i(t,200,{items:a});return}if(c==="/market"&&o.method==="POST"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}let a;try{a=JSON.parse(await p(o))}catch{i(t,400,{error:"Invalid JSON body"});return}const e={libraries:r.libraries,dataDir:r.libraries.dataDir};try{if(a.kind==="dir"&&a.path){const n=await xt(a.path,e);n.refreshed&&r.fingerprintCache?.invalidate(n.dir),await k(n.dir),r.dataBus?.emit({key:"market"}),i(t,201,{item:await M(r.libraries.getById(n.libraryId),r.libraries.dataDir)})}else if(a.kind==="git"&&a.url){const n=await Kt(a.url,e);n.refreshed&&r.fingerprintCache?.invalidate(n.dir),await k(n.dir),r.dataBus?.emit({key:"market"}),i(t,201,{item:await M(r.libraries.getById(n.libraryId),r.libraries.dataDir)})}else i(t,400,{error:'Invalid body (kind: "dir" + path | "git" + url)'})}catch(n){i(t,400,{error:n instanceof Error?n.message:"Import failed"})}return}if(c==="/market/check-updates"&&o.method==="POST"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}let a={};const e=(await p(o)).trim();if(e)try{a=JSON.parse(e)}catch{i(t,400,{error:"Invalid JSON body"});return}const n=r.libraries.list().filter(l=>!E(l.path,r.libraries.dataDir)).filter(l=>!a.ids?.length||a.ids.includes(l.id));for(const l of n)await k(l.path);const s=await Qt(n.map(l=>({id:l.id,dir:l.path})));r.dataBus?.emit({key:"market"}),i(t,200,{results:s});return}if(c==="/market/refresh"&&o.method==="POST"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}let a={};const e=(await p(o)).trim();if(e)try{a=JSON.parse(e)}catch{i(t,400,{error:"Invalid JSON body"});return}const n=r.libraries.list().filter(h=>!a.ids?.length||a.ids.includes(h.id)),s=[];for(const h of n.filter(d=>E(d.path,r.libraries.dataDir))){const d=await Yt({libraries:r.libraries});s.push({id:h.id,status:d.error?"failed":"refreshed"})}const l=n.filter(h=>!E(h.path,r.libraries.dataDir)).map(h=>({id:h.id,dir:h.path})),m=[...await Xt(l,{libraries:r.libraries,dataDir:r.libraries.dataDir}),...s],f=new Map(l.map(h=>[h.id,h.dir]));for(const h of m)if(h.status==="refreshed"){const d=f.get(h.id);d&&(r.fingerprintCache?.invalidate(d),await k(d))}r.dataBus?.emit({key:"market"}),i(t,200,{results:m});return}const L=c.match(/^\/market\/(\d+)$/);if(L&&o.method==="DELETE"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}const a=r.libraries.getById(Number(L[1]));if(!a){i(t,404,{error:"Library not found"});return}if(E(a.path,r.libraries.dataDir)){i(t,409,{error:"Built-in market is owned by weave and cannot be deleted",note:"It is re-materialized from @weavecode/templates on every daemon start."});return}if(!J(a.path,A(r.libraries.dataDir))){i(t,400,{error:"Not a market resource (legacy row \u2014 use DELETE /libraries/:id)"});return}let e;try{await(r.pluginSurface??dt("claude")).removeMarketplace(a.path)}catch(n){e=`Host marketplace deregistration failed: ${n instanceof Error?n.message:String(n)}`}await Zt(a.path),r.libraries.remove(a.id),await k(a.path),r.dataBus?.emit({key:"market"}),r.dataBus?.emit({key:"libraries"}),i(t,200,{ok:!0,removed:a.path,installedCopiesKept:!0,...e?{warning:e}:{},note:"The host cascade-uninstalls plugins installed from this market; other installed copies are kept."});return}const H=c.match(/^\/libraries\/(\d+)\/market-import$/);if(H&&o.method==="POST"){if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}const a=r.libraries.getById(Number(H[1]));if(!a){i(t,404,{error:"Library not found"});return}try{const e=a.path,n=await Wt(a,{libraries:r.libraries,dataDir:r.libraries.dataDir});await k(e),await k(n.dir),i(t,200,{item:await M(r.libraries.getById(n.libraryId),r.libraries.dataDir)})}catch(e){i(t,400,{error:e instanceof Error?e.message:"Import failed"})}return}const U=c.match(/^\/projects\/(\d+)\/install$/);if(U&&o.method==="POST"){const a=r.projects.getById(Number(U[1]));let e;try{e=JSON.parse(await p(o))}catch{i(t,400,{error:"Invalid JSON body"});return}if(!a&>(e)){i(t,404,{error:"Project not found"});return}const n=a?.path??"";try{if(e.category==="skill"){if(!e.name||!e.sourceDir){i(t,400,{error:'Missing "name" or "sourceDir"'});return}const s=e.agent??"claude",l=e.scope==="user"?await Ot(e.sourceDir,e.name,s):await vt(n,e.sourceDir,e.name,s);i(t,201,{category:"skill",name:e.name,scope:e.scope??"project",agent:s,path:l})}else if(e.category&&nt(e.category)){if(!e.name||!e.sourceFile){i(t,400,{error:'Missing "name" or "sourceFile"'});return}const s=e.agent??"claude",l=e.scope??"project",m=await It(n,e.category,e.name,e.sourceFile,s,l);i(t,201,{category:e.category,name:e.name,scope:l,agent:s,path:m})}else if(e.category==="mcp"){if(!e.name||!e.mcpConfig){i(t,400,{error:'Missing "name" or "mcpConfig"'});return}const s=e.agent??"claude",l=F(s);if(!l){i(t,400,{error:`Unsupported agent: ${s}`});return}const m=l.mcpScopes(),f=e.scope??m[0];if(!f||!m.includes(f)){i(t,400,{error:`MCP servers for ${s} are ${m.join("/")}-level only \u2014 scope "${e.scope}" is not available`});return}if(e.name in await l.mcpRead(f,n,$())){O(t,new ot(`MCP server "${e.name}" is already configured`));return}await l.mcpWrite(f,n,$(),e.name,e.mcpConfig),f==="project"&&await Dt(n,{category:"mcp",name:e.name,agent:s}),i(t,201,{category:"mcp",name:e.name,scope:f,agent:s})}else if(e.category==="plugin"){if(!e.name||!e.sourceDir){i(t,400,{error:'Missing "name" or "sourceDir"'});return}if(e.scope&&e.scope!=="user"){i(t,400,{error:'Plugin install currently only supports scope "user"'});return}const s=e.agent??"claude",l=ft().find(h=>h.harness===s);if(!l){i(t,400,{error:`No plugin install surface for runtime: ${s}`});return}const m=s==="claude"&&r.pluginSurface?r.pluginSurface:l.surface,f=await Be(m,{dir:e.sourceDir,name:e.name,version:e.version,description:e.description});r.dataBus?.emit({key:"toolkit"}),i(t,201,{category:"plugin",key:f.key,scope:"user",agent:s,alreadyInstalled:f.alreadyInstalled})}else i(t,400,{error:'Invalid "category" (skill|mcp|plugin|command|agent|workflow|rule|output-style)'})}catch(s){O(t,s)}return}const x=c.match(/^\/projects\/(\d+)\/uninstall$/);if(x&&o.method==="POST"){const a=r.projects.getById(Number(x[1]));let e;try{e=JSON.parse(await p(o))}catch{i(t,400,{error:"Invalid JSON body"});return}if(!a&>(e)){i(t,404,{error:"Project not found"});return}const n=a?.path??"";try{if(e.category==="skill"){if(!e.name){i(t,400,{error:'Missing "name"'});return}const s=e.agent??"claude";e.scope==="user"?await Mt(e.name,s):await Pt(n,e.name,s)}else if(e.category&&nt(e.category)){if(!e.name){i(t,400,{error:'Missing "name"'});return}await Et(n,e.category,e.name,e.agent??"claude",e.scope??"project")}else if(e.category==="mcp"){if(!e.name){i(t,400,{error:'Missing "name"'});return}const s=e.agent??"claude",l=F(s);if(!l){i(t,400,{error:`Unsupported agent: ${s}`});return}const m=l.mcpScopes(),f=e.scope??m[0];if(!f||!m.includes(f)){i(t,400,{error:`MCP servers for ${s} are ${m.join("/")}-level only \u2014 scope "${e.scope}" is not available`});return}if(!await l.mcpRemove(f,n,$(),e.name)){O(t,new G(`MCP server "${e.name}" is not configured`));return}f==="project"&&await Jt(n,{category:"mcp",name:e.name,agent:s})}else{i(t,400,{error:'Invalid "category" (skill|mcp|command|agent|workflow|rule|output-style)'});return}i(t,200,{ok:!0})}catch(s){O(t,s)}return}const K=c.match(/^\/projects\/(\d+)\/file$/);if(K&&o.method==="GET"){const a=r.projects.getById(Number(K[1]));if(!a){i(t,404,{error:"Project not found"});return}const e=u.searchParams.get("path");if(!e){i(t,400,{error:'Missing "path" query parameter'});return}try{const n=await qt(a.path,e);i(t,200,{path:e,content:n.content,size:n.size})}catch(n){R(t,n)}return}if(c==="/global-file"&&o.method==="GET"){const a=u.searchParams.get("path");if(!a){i(t,400,{error:'Missing "path" query parameter'});return}try{const e=await te(a);i(t,200,{path:a,content:e.content,size:e.size})}catch(e){R(t,e)}return}const W=c.match(/^\/projects\/(\d+)\/mcp$/);if(W&&o.method==="GET"){const a=r.projects.getById(Number(W[1]));if(!a){i(t,404,{error:"Project not found"});return}i(t,200,{rows:await Tt(a.path)});return}const _=c.match(/^\/projects\/(\d+)\/harness-checks$/);if(_&&o.method==="GET"){const a=r.projects.getById(Number(_[1]));if(!a){i(t,404,{error:"Project not found"});return}i(t,200,{checks:await wt(a.path)});return}if(c.match(/^\/projects\/(\d+)\/skills\/global$/)&&o.method==="GET"){i(t,200,{groups:await Bt()});return}if(c.match(/^\/projects\/(\d+)\/skills\/global\/files$/)&&o.method==="GET"){const a=u.searchParams.get("source"),e=u.searchParams.get("name"),n=u.searchParams.get("pluginKey")??void 0;if(!a||!e){i(t,400,{error:'Missing "source" or "name" query parameter'});return}const s=await st(a,e,n);if(!s){i(t,404,{error:"Skill not found"});return}i(t,200,{files:await Ct(s)});return}if(c.match(/^\/projects\/(\d+)\/skills\/global\/file$/)&&o.method==="GET"){const a=u.searchParams.get("source"),e=u.searchParams.get("name"),n=u.searchParams.get("pluginKey")??void 0,s=u.searchParams.get("path");if(!a||!e||!s){i(t,400,{error:'Missing "source", "name" or "path" query parameter'});return}const l=await st(a,e,n);if(!l){i(t,404,{error:"Skill not found"});return}try{const m=await $t(l,s);i(t,200,{path:s,content:m.content,size:m.size})}catch(m){R(t,m)}return}const z=c.match(/^\/projects\/(\d+)\/plugins\/state$/);if(z){const a=r.projects.getById(Number(z[1]));if(!a){i(t,404,{error:"Project not found"});return}if(o.method==="GET"){i(t,200,{rows:await Me(a.path)});return}if(o.method==="PUT"){let e;try{e=JSON.parse(await p(o))}catch{i(t,400,{error:"Invalid JSON body"});return}if(!C(e.runtime)||!e.key||!e.scope||!["user","project","local"].includes(e.scope)||!e.state||!["on","off","inherit"].includes(e.state)){i(t,400,{error:'Missing or invalid "runtime"/"key"/"scope" (user|project|local)/"state" (on|off|inherit)'});return}const n=e.scope,s=e.state;try{const l=ft().find(m=>m.harness===e.runtime);if(l)await(e.runtime==="claude"&&r.pluginSurface?r.pluginSurface:l.surface).setEnabledOverride(a.path,n,e.key,s);else{const m=Oe().find(f=>f.harness===e.runtime);if(!m||n!=="user"||s==="inherit"){i(t,400,{error:"This runtime only supports user-scope on/off overrides"});return}await m.state.writeGlobalEnabled(e.key,s==="on")}r.dataBus?.emit({key:"toolkit"}),i(t,200,{ok:!0})}catch(l){i(t,400,{error:l instanceof Error?l.message:"Plugin state write failed"})}return}}if(c.match(/^\/projects\/(\d+)\/plugins\/uninstall$/)&&o.method==="POST"){let a;try{a=JSON.parse(await p(o))}catch{i(t,400,{error:"Invalid JSON body"});return}if(!C(a.runtime)||!a.key){i(t,400,{error:'Missing or invalid "runtime"/"key"'});return}try{const e=a.runtime==="claude"&&r.pluginSurface?r.pluginSurface:dt(a.runtime),{name:n,market:s}=e.parsePluginKey(a.key);await e.uninstall(n,s),i(t,200,{ok:!0})}catch(e){i(t,400,{error:e instanceof Error?e.message:"Plugin uninstall failed"})}return}const V=c.match(/^\/projects\/(\d+)\/mcp\/enable$/);if(V){const a=r.projects.getById(Number(V[1]));if(!a){i(t,404,{error:"Project not found"});return}if(o.method==="PUT"){let e;try{e=JSON.parse(await p(o))}catch{i(t,400,{error:"Invalid JSON body"});return}if(!e.name||!e.state){i(t,400,{error:'Missing "name" or "state"'});return}if(!["on","off","inherit"].includes(e.state)){i(t,400,{error:'Invalid "state" (on|off|inherit)'});return}await Ft(a.path,e.name,e.state),i(t,200,{ok:!0});return}}const Q=c.match(/^\/projects\/(\d+)\/updates$/);if(Q&&o.method==="GET"){const a=r.projects.getById(Number(Q[1]));if(!a){i(t,404,{error:"Project not found"});return}const e=r.fingerprintCache??new D;if(!r.libraries){const s=await le(a.path,e),l=await ue(a.path);i(t,200,{skills:[],mcp:[],files:[],pluginTemplates:s,initFiles:l,available:s.filter(m=>m.outdated).length+l.filter(m=>m.outdated&&m.syncClass==="auto").length,needsInit:me(l)});return}const n=await ce(a.path,r.libraries.list(),e);i(t,200,n);return}const X=c.match(/^\/projects\/(\d+)\/update$/);if(X&&o.method==="POST"){const a=r.projects.getById(Number(X[1]));if(!a){i(t,404,{error:"Project not found"});return}if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}let e;try{e=JSON.parse(await p(o))}catch{i(t,400,{error:"Invalid JSON body"});return}if(!e.category||!e.name||!e.scope){i(t,400,{error:'Missing "category", "name", or "scope"'});return}if(!["skill","mcp","command","agent","workflow","rule","output-style"].includes(e.category)){i(t,400,{error:'Invalid "category"'});return}try{const s=await Ut(r.libraries.list());await Lt(a.path,{category:e.category,name:e.name,scope:e.scope,agent:e.agent},s),i(t,200,{ok:!0,category:e.category,name:e.name,scope:e.scope})}catch(s){i(t,400,{error:s instanceof Error?s.message:"Update failed"})}return}const Y=c.match(/^\/projects\/(\d+)\/sync-all$/);if(Y&&o.method==="POST"){const a=r.projects.getById(Number(Y[1]));if(!a){i(t,404,{error:"Project not found"});return}if(!r.libraries){i(t,503,{error:"Libraries unavailable"});return}try{const e=r.fingerprintCache??new D,n=await Ht(a.path,r.libraries.list(),e);let s=n;try{const l=r.hookPlugins?.list().map(f=>f.id)??[],m=St(a,l,{projects:r.projects});s={...n,pluginSync:{syncedAt:m}}}catch{}i(t,200,s),r.updatesRefresher?.refreshNow(!0)}catch(e){i(t,400,{error:e instanceof Error?e.message:"Sync failed"})}return}const Z=c.match(/^\/projects\/(\d+)\/config$/);if(Z){const a=r.projects.getById(Number(Z[1]));if(!a){i(t,404,{error:"Project not found"});return}if(o.method==="GET"){i(t,200,await ct(a.path));return}if(o.method==="PUT"){let e;try{e=JSON.parse(await p(o))}catch{i(t,400,{error:"Invalid JSON body"});return}const n=await ct(a.path),s={lastSyncAt:e.lastSyncAt??n.lastSyncAt};await Rt(a.path,s),i(t,200,s);return}}if(c==="/statusline/global"){if(o.method==="GET"){i(t,200,{config:await mt()});return}if(o.method==="PUT"){let a;try{a=JSON.parse(await p(o))}catch{i(t,400,{error:"Invalid JSON body"});return}try{await ne(a);for(const e of r.projects.list()){const n=await lt(e.path);n.source==="global"&&await ut(e.path,n)}r.dataBus?.emit({key:"statusline"}),i(t,200,{ok:!0})}catch(e){i(t,400,{error:e instanceof Error?e.message:"Global statusline update failed"})}return}}if(c==="/statusline/default"&&o.method==="GET"){i(t,200,{config:se});return}const q=c.match(/^\/projects\/(\d+)\/statusline$/);if(q){const a=r.projects.getById(Number(q[1]));if(!a){i(t,404,{error:"Project not found"});return}if(o.method==="GET"){const[e,n,s]=await Promise.all([lt(a.path),ie(a.path),mt()]);i(t,200,{config:e,script:n,globalConfig:s,facts:oe(a.path)});return}if(o.method==="PUT"){let e;try{e=JSON.parse(await p(o))}catch{i(t,400,{error:"Invalid JSON body"});return}try{await ut(a.path,e),r.dataBus?.emit({key:"statusline",projectPath:a.path}),i(t,200,{ok:!0})}catch(n){i(t,400,{error:n instanceof Error?n.message:"Statusline update failed"})}return}}if(c==="/events"&&o.method==="GET"){Le(o,t,r,u);return}if(c==="/assets"&&o.method==="GET"){const a=u.searchParams.get("path");if(!a){i(t,400,{error:'Missing "path" query parameter'});return}if(!r.watch){i(t,503,{error:"Watch service unavailable"});return}const e=await ye(a),n=r.watch.getAssets(a).map(s=>{const l=e.get(s.relPath);return l?{...s,namespace:l}:s});i(t,200,{projectPath:a,assets:n});return}if(c==="/global-file-assets"&&o.method==="GET"){i(t,200,{assets:await ae()});return}if(c==="/projects"&&o.method==="GET"){i(t,200,{projects:await ht(r.projects.list(),r.harnesses)});return}if(c==="/projects/archived"&&o.method==="GET"){i(t,200,{projects:await ht(r.projects.listArchived(),r.harnesses)});return}const tt=c.match(/^\/projects\/(\d+)\/archive$/);if(tt&&o.method==="POST"){const a=r.projects.getById(Number(tt[1]));if(!a){i(t,404,{error:"Project not found"});return}if(a.archivedAt){i(t,200,{project:a,purged:{events:0,dispatches:0,sessions:0,messages:0}});return}const e=r.hookDispatches?.deleteByProject(a.id)??0,n=r.hookEvents?.deleteByProject(a.id)??0;r.uploads?.pruneUnreferenced();const s=r.messageDeliverer?.forgetProject(a.id)??0;r.sessionWatches?.deleteByProject(a.id),r.sessionSequences?.deleteByProject(a.id),r.sideQuestions?.repo.deleteByProject(a.id);const l=r.hookSessions?.removeByProject(a.id)??0;r.projects.setArchived(a.id,!0),ge(r.hooksDir??jt(),a.token),r.watch?.reconcile(),r.dataBus?.emit({key:"projects"}),i(t,200,{project:r.projects.getById(a.id),purged:{events:n,dispatches:e,sessions:l,messages:s}});return}const et=c.match(/^\/projects\/(\d+)\/restore$/);if(et&&o.method==="POST"){const a=r.projects.getById(Number(et[1]));if(!a){i(t,404,{error:"Project not found"});return}if(!a.archivedAt){i(t,200,{project:a});return}r.projects.setArchived(a.id,!1),r.hookMirror?.syncProject(a.id),r.watch?.reconcile(),r.dataBus?.emit({key:"projects"}),i(t,200,{project:r.projects.getById(a.id)});return}if(c==="/projects/order"&&o.method==="PUT"){let a;try{a=JSON.parse(await p(o))}catch{i(t,400,{error:"Invalid JSON body"});return}if(!Array.isArray(a.ids)||!a.ids.every(e=>Number.isInteger(e))){i(t,400,{error:'Invalid "ids" (integer array expected)'});return}r.projects.setOrder(a.ids),r.dataBus?.emit({key:"projects"}),i(t,200,{projects:r.projects.list()});return}const rt=c.match(/^\/projects\/(\d+)\/panes$/);if(rt&&o.method==="GET"){const a=r.projects.getById(Number(rt[1]));if(!a){i(t,404,{error:"Project not found"});return}const e=await P(),n=r.opener?.listPanes??I.listPanes;try{i(t,200,{projectId:a.id,panes:await n(a.path,e.terminal)})}catch(s){i(t,409,{error:s instanceof Error?s.message:"Cannot list panes"})}return}const at=c.match(/^\/projects\/(\d+)\/terminals$/);if(at&&o.method==="GET"){const a=r.projects.getById(Number(at[1]));if(!a){i(t,404,{error:"Project not found"});return}const e=await P(),n=r.opener?.listTerminals??I.listTerminals,s=Ie(e.terminal).enumerable?await n(a.path,e.terminal):[];i(t,200,{projectId:a.id,terminals:s});return}if(c==="/terminals/counts"&&o.method==="GET"){if(r.terminalRefresher)i(t,200,{counts:r.terminalRefresher.lastSnapshot?.counts??{}}),r.terminalRefresher.tick(!0);else{const a=r.opener??I;i(t,200,{counts:await Pe(r.projects.list(),a)})}return}if(o.method==="GET"&&r.staticDir){xe(t,c,r.staticDir);return}i(t,404,{error:"Not found"})}const Re=25e3;function Le(o,t,r,u){const c=r.sseHub;if(!c||!r.watch&&!r.hookBus&&!r.dataBus){t.statusCode=503,t.setHeader("content-type","application/json"),t.end(JSON.stringify({error:"Event stream unavailable"}));return}const b=o.headers["last-event-id"],g=(Array.isArray(b)?b[0]:b)??u.searchParams.get("since")??null,y=c.replay(g);t.writeHead(200,{"content-type":"text/event-stream","cache-control":"no-cache",connection:"keep-alive","x-accel-buffering":"no"}),t.write(`: connected
|
|
2
2
|
|
|
3
|
-
`);const
|
|
3
|
+
`);const w=setInterval(()=>{t.writableEnded||t.write(`data: ${JSON.stringify({type:"ping"})}
|
|
4
4
|
|
|
5
|
-
`)})),
|
|
5
|
+
`)},Re);w.unref?.(),y.gap&&t.write(`data: ${JSON.stringify({type:"resync"})}
|
|
6
6
|
|
|
7
|
-
`)
|
|
7
|
+
`);for(const j of y.frames)t.write(`id: ${j.id}
|
|
8
|
+
data: ${j.data}
|
|
8
9
|
|
|
9
|
-
`)
|
|
10
|
+
`);const N=c.subscribe(j=>{t.write(`id: ${j.id}
|
|
11
|
+
data: ${j.data}
|
|
10
12
|
|
|
11
|
-
`)})
|
|
13
|
+
`)});r.inflightRefresher?.refreshNow(),r.terminalRefresher?.onSubscriberJoin(),r.updatesRefresher?.onSubscriberJoin(),o.on("close",()=>{clearInterval(w),N()})}function He(o,t,r){if(t==="/health")return!0;if(o.method!=="GET")return!1;if((o.headers.accept??"").includes("text/html"))return!0;if(!r.staticDir)return!1;const u=v.normalize(t).replace(/^([/\\]\.\.[/\\])+/,""),c=v.join(r.staticDir,u);return c.startsWith(r.staticDir)&&S.existsSync(c)&&S.statSync(c).isFile()}function Ue(o,t){o.setHeader("cache-control",t.startsWith("/assets/")?"public, max-age=31536000, immutable":"no-cache")}function xe(o,t,r){const u=v.normalize(t).replace(/^([/\\]\.\.[/\\])+/,""),c=v.join(r,u);if(!c.startsWith(r)){o.statusCode=403,o.setHeader("content-type","application/json"),o.end(JSON.stringify({error:"Forbidden"}));return}S.stat(c,(b,g)=>{if(!b&&g.isFile()){o.setHeader("content-type",We(c)),Ue(o,t),o.setHeader("content-length",String(g.size)),S.createReadStream(c).pipe(o);return}S.readFile(v.join(r,"index.html"),(y,w)=>{if(y){o.statusCode=404,o.setHeader("content-type","application/json"),o.end(JSON.stringify({error:"Not found"}));return}o.setHeader("content-type","text/html"),o.setHeader("cache-control","no-cache"),o.end(w)})})}function pt(o,t){const r=o.ai??je;return{...o,ai:{baseUrl:r.baseUrl,model:r.model,apiKeySet:Ge(t)}}}function Ke(o){if(!o)return;const{apiKey:t,...r}=o;return r}function We(o){return o.endsWith(".js")?"text/javascript":o.endsWith(".css")?"text/css":o.endsWith(".html")?"text/html":o.endsWith(".svg")?"image/svg+xml":o.endsWith(".json")?"application/json":"application/octet-stream"}function _e(o){const t=Number(o??"100");return!Number.isFinite(t)||t<=0?100:Math.min(Math.floor(t),500)}function gt(o){return o.scope==="user"||o.category==="plugin"?!1:o.category==="mcp"?!!F(o.agent)?.mcpScopes().includes("project"):!0}function F(o){const t=o??"claude";return Te().find(r=>r.harness===t)}function O(o,t){t instanceof ot?o.statusCode=409:t instanceof G?o.statusCode=404:t instanceof Gt||t instanceof At?o.statusCode=500:o.statusCode=400,o.setHeader("content-type","application/json"),o.end(JSON.stringify({error:t instanceof Error?t.message:"Install failed"}))}async function M(o,t){const r=J(o.path,A(t)),u=r?await _t(o.path):null,c=E(o.path,t);return{id:o.id,kind:o.kind,addedAt:o.addedAt,path:o.path,relPath:J(o.path,t)?Vt(o.path,t):null,resourceId:r?v.basename(o.path):null,managed:r,builtin:c,namespace:r?zt(o.path,A(t)):void 0,legacy:!u&&!c,source:c?"builtin":u?.source??"legacy",origin:u?.origin??null,importedAt:u?.importedAt??null,commit:u?.commit??null,tags:await Qe(o.path)}}const ze={command:"command",agent:"agent",workflow:"workflow",rule:"rules","output-style":"output-style"};async function Ve(o){const{plugins:t,skills:r,mcp:u,files:c}=await it(o),b={},g=(y,w)=>{w>0&&(b[y]=(b[y]??0)+w)};g("skill",r.length),g("mcp",u.length);for(const[y,w]of Object.entries(c))g(ze[y]??y,w.length);return g("plugin",t.length),g("mods",t.filter(y=>y.kind==="mod"||y.components.includes("mod")).length),b}async function Qe(o){try{return await Ve(o)}catch{return{}}}function R(o,t){t instanceof ee?o.statusCode=403:t instanceof re?o.statusCode=413:t instanceof G?o.statusCode=404:o.statusCode=400,o.setHeader("content-type","application/json"),o.end(JSON.stringify({error:t instanceof Error?t.message:"Read failed"}))}
|
|
@@ -33,11 +33,24 @@ export declare class SessionProbe {
|
|
|
33
33
|
* 换模型这件事得由事件先说,等转录自己跟上(照会的值不再是 `stale`)才交还给它。
|
|
34
34
|
*/
|
|
35
35
|
private readonly switched;
|
|
36
|
+
/** sessionId → 推理档位(每次带 `effort` 的事件覆盖):状态栏 `thinking` 块的事实在 hook 里。 */
|
|
37
|
+
private readonly efforts;
|
|
36
38
|
noteTranscript(sessionId: string, path: string | null): void;
|
|
37
39
|
private pathOf;
|
|
38
40
|
forget(sessionId: string): void;
|
|
41
|
+
/** 推理档位(`effort.level`,事件自带):逐条覆盖,与模型/模式同口径只放内存。 */
|
|
42
|
+
noteEffort(sessionId: string, level: string): void;
|
|
43
|
+
/** 最后已知的推理档位;没上报过即 null(不猜)。 */
|
|
44
|
+
currentEffort(sessionId: string): string | null;
|
|
39
45
|
/** 宿主落定的换模型(`PostModelSwitch` 的 `to_model`):立刻生效,见 `switched`。 */
|
|
40
46
|
noteModel(sessionId: string, model: string): void;
|
|
47
|
+
/**
|
|
48
|
+
* 宿主开 / 恢复会话(`SessionStart`):模型由宿主重新决定,此前的「最后已知」与
|
|
49
|
+
* 换模型闩一律作废、下一次问重新读——`--resume` 起来会落回默认模型,而转录里还
|
|
50
|
+
* 只有上一轮的照会,旧答案既可能不是此刻的模型、也会让 `switched` 的 stale 对照
|
|
51
|
+
* 永远对不上(新照会恰好等于 stale 时标签就再也改不回来)。带了真名就以它为准。
|
|
52
|
+
*/
|
|
53
|
+
sessionStarted(sessionId: string, model: string | null): void;
|
|
41
54
|
/**
|
|
42
55
|
* 转录还在不在:true/false;路径都不知道 → null(不猜)。
|
|
43
56
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{closeSync as
|
|
1
|
+
import{closeSync as a,openSync as c,readSync as i,statSync as o}from"node:fs";import{currentModelAt as h,currentModeAt as u,interruptMarkerAt as f}from"@weavecode/harness";const d=64*1024,m={model:null,mode:null};export function readTail(s,t=d){try{const e=o(s).size,r=Math.max(0,e-t),n=c(s,"r");try{const l=Buffer.alloc(e-r);return i(n,l,0,l.length,r),l.toString("utf8")}finally{a(n)}}catch{return null}}export function dbTranscriptPathResolver(s){return t=>{try{const e=JSON.parse(s.listBySession(t,1)[0]?.payload??"");return typeof e.transcriptPath=="string"&&e.transcriptPath?e.transcriptPath:null}catch{return null}}}export class SessionProbe{fallbackPath;constructor(t){this.fallbackPath=t}paths=new Map;marks=new Map;known=new Map;switched=new Map;efforts=new Map;noteTranscript(t,e){e&&this.paths.set(t,e)}pathOf(t){const e=this.paths.get(t);if(e)return e;const r=this.fallbackPath?.(t)??null;return r&&this.paths.set(t,r),r}forget(t){this.paths.delete(t),this.known.delete(t),this.switched.delete(t),this.efforts.delete(t)}noteEffort(t,e){this.efforts.set(t,e)}currentEffort(t){return this.efforts.get(t)??null}noteModel(t,e){const r=this.pathOf(t);this.switched.set(t,{stale:r?this.factsOf(r).model:null,model:e})}sessionStarted(t,e){this.known.delete(t),this.switched.delete(t),e&&this.noteModel(t,e)}transcriptPresent(t){const e=this.pathOf(t);return e?this.factsOf(e).size>0:null}interruptedAt(t,e){const r=this.pathOf(t);if(!r)return null;const n=this.factsOf(r).markerAt;if(n===null)return null;const l=Date.parse(e);return Number.isFinite(l)&&n>l?n:null}currentModel(t){return this.latched(t).model}currentMode(t){return this.latched(t).mode}latched(t){const e=this.pathOf(t),r=e?this.factsOf(e):m,n=this.known.get(t),l={model:this.switchedModel(t,r.model)??n?.model??null,mode:r.mode??n?.mode??null};return(!n||n.model!==l.model||n.mode!==l.mode)&&this.known.set(t,l),l}switchedModel(t,e){const r=this.switched.get(t);return r&&e===r.stale?r.model:e}factsOf(t){let e;try{e=o(t).size}catch{return{size:-1,markerAt:null,model:null,mode:null}}const r=this.marks.get(t);if(r&&r.size===e)return r;const n=readTail(t),l={size:e,markerAt:n===null?null:f(n),model:n===null?null:h(n),mode:n===null?null:u(n)};return this.marks.set(t,l),l}}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DataBus, DataKey } from './data-bus.js';
|
|
2
2
|
/**
|
|
3
3
|
* SnapshotRefresher — 「前端不轮询,后端有人看时定期探、只在值变化时推绝对值快照」
|
|
4
|
-
*
|
|
4
|
+
* 的通用骨架(使用者:会话存活 pidAlive、会话中断态、状态栏底条的 git 事实)。
|
|
5
5
|
*
|
|
6
6
|
* 为什么要有它:这两类事实都可能**没有** hook 事件来触发变化(claude 进程被杀 =
|
|
7
7
|
* 无 SessionEnd;用户按 ESC 打断 = 无任何事件),GET /sessions 里算好的值于是卡在
|
|
@@ -15,6 +15,12 @@ export interface SnapshotRefresherOptions {
|
|
|
15
15
|
intervalMs?: number;
|
|
16
16
|
/** 是否值得探测(一般为「有 SSE 客户端」)。缺省恒真(测试用)。 */
|
|
17
17
|
shouldRun?: () => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* 只发失效帧(不带 payload):前端按 key 调既有 fetcher 重拉,而不是拿这份
|
|
20
|
+
* 快照直写状态。用于「快照是别的进程算的、前端已经有一份更完整的取数口」的域
|
|
21
|
+
* (如 statusline 底条的 git 事实:同一份 GET 里还带着配置与脚本)。
|
|
22
|
+
*/
|
|
23
|
+
invalidateOnly?: boolean;
|
|
18
24
|
}
|
|
19
25
|
export declare class SnapshotRefresher {
|
|
20
26
|
/** 现算一份载荷(前端直接写状态的那份绝对值快照)。 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export class SnapshotRefresher{snapshot;bus;options;timer;lastFingerprint=null;constructor(t,s
|
|
1
|
+
export class SnapshotRefresher{snapshot;bus;options;timer;lastFingerprint=null;constructor(t,i,s){this.snapshot=t,this.bus=i,this.options=s}start(){this.timer||(this.timer=setInterval(()=>this.tick(),this.options.intervalMs??15e3))}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0)}tick(){if(!(this.options.shouldRun&&!this.options.shouldRun()))try{const t=this.snapshot(),i=JSON.stringify(t);if(i===this.lastFingerprint)return;this.lastFingerprint=i,this.bus.emit(this.options.invalidateOnly?{key:this.options.key}:{key:this.options.key,payload:t})}catch{}}}
|