@cuongtran001/kanna 1.2.0 → 1.3.1
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/dist/client/assets/{arc-BLk1CZKB.js → arc-DXtN3EWJ.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-B01ZQYte.js → architectureDiagram-3BPJPVTR-CIGqW1z-.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-diL6a3cq.js → blockDiagram-GPEHLZMM-BafNS_H6.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-BY1verL8.js → c4Diagram-AAUBKEIU-YdAgRSis.js} +1 -1
- package/dist/client/assets/channel-BEYTrUAm.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-psdZuajw.js → chunk-2J33WTMH-eiP5y5G-.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-CrwQVBc5.js → chunk-4BX2VUAB-CvLGzPMF.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-9-aj4jKO.js → chunk-55IACEB6-Y-S9Fb8A.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-1FNK9_nB.js → chunk-727SXJPM-6sJH1RI9.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DFhvd_Ek.js → chunk-AQP2D5EJ-hV1r_IXO.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-B7MLrDa7.js → chunk-FMBD7UC4-CC3L28S5.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-D4Df05QE.js → chunk-ND2GUHAM-BZ3czhdH.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-Iq17Dznm.js → chunk-QZHKN3VN-Bjyd6Yr-.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-CvnF5Ud6.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CvnF5Ud6.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-BmaZhtXy.js → cose-bilkent-S5V4N54A-CfxdDghE.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-BCmpfnfP.js → dagre-BM42HDAG-B8CddsbB.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-BIEcKowS.js → diagram-2AECGRRQ-F0yS0fVB.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-CRBABBfv.js → diagram-5GNKFQAL-cW0i0qlh.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-BRxUk16N.js → diagram-KO2AKTUF-BzAKMeJT.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DPBs4xl1.js → diagram-LMA3HP47-qdC4XqIl.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-BE9rgVv8.js → diagram-OG6HWLK6-CGcpi6Dh.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-D3vgA1xJ.js → erDiagram-TEJ5UH35-CxNTDicM.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-Cgu-8XXC.js → flowDiagram-I6XJVG4X-qkGa5TqY.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-CgjXVyTR.js → ganttDiagram-6RSMTGT7-D9mVPoQi.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-zD5D8B-i.js → gitGraphDiagram-PVQCEYII-DA0grAXe.js} +1 -1
- package/dist/client/assets/{index-DBTdhMbJ.js → index-BqUY1Bm4.js} +1 -1
- package/dist/client/assets/index-CLsOWXff.css +32 -0
- package/dist/client/assets/{index-zQLLeAfl.js → index-TU1k6ov6.js} +253 -243
- package/dist/client/assets/{infoDiagram-5YYISTIA-BaFHqT4u.js → infoDiagram-5YYISTIA-CGljtLs3.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-2yAC1br2.js → ishikawaDiagram-YF4QCWOH-CtC0xrmG.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-DacQ-ND5.js → journeyDiagram-JHISSGLW-URLnVZeN.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-Qlbwm3z4.js → kanban-definition-UN3LZRKU-C2Q6aqQh.js} +1 -1
- package/dist/client/assets/{linear-DeafqpgO.js → linear-ByKHtsIx.js} +1 -1
- package/dist/client/assets/{mermaid.core-KYiCPJDj.js → mermaid.core-Dl_f2KLc.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-B1V-T6d3.js → mindmap-definition-RKZ34NQL-CaJ9IDLI.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-DOF9OFRu.js → pieDiagram-4H26LBE5-mmYaf2Oa.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-lH1mB7ko.js → quadrantDiagram-W4KKPZXB-B_PO6OCC.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-BaTsJ_sS.js → requirementDiagram-4Y6WPE33-M4huwJt1.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Pfxib5iI.js → sankeyDiagram-5OEKKPKP-B0hb1sWh.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BQMEKkBC.js → sequenceDiagram-3UESZ5HK-BAqSptnA.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-CuldNnzp.js → stateDiagram-AJRCARHV-DTNkvPi5.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CVN3oGTF.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-DP7u0YAg.js → timeline-definition-PNZ67QCA-MgAjHz8Q.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-QlrjpNeQ.js → vennDiagram-CIIHVFJN-DmKoO51W.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-BqdJ_sAM.js → wardley-L42UT6IY-CWcyHnYs.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Wi3kaQ5X.js → wardleyDiagram-YWT4CUSO-BGIGmFP9.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-D-KXMyCG.js → xychartDiagram-2RQKCTM6-D20YIvEl.js} +1 -1
- package/dist/client/index.html +2 -2
- package/package.json +1 -1
- package/src/server/agent-coordinator-types.ts +158 -0
- package/src/server/agent-coordinator.ts +919 -0
- package/src/server/agent-deps-builders.ts +425 -0
- package/src/server/agent.oauth-rotation.test.ts +101 -0
- package/src/server/agent.test.ts +24 -8
- package/src/server/agent.ts +35 -4963
- package/src/server/auto-continue/read-model.test.ts +2 -2
- package/src/server/auto-continue/read-model.ts +4 -1
- package/src/server/claude-autocontinue-commands.test.ts +494 -0
- package/src/server/claude-autocontinue-commands.ts +256 -0
- package/src/server/claude-cancel-handler.test.ts +472 -0
- package/src/server/claude-cancel-handler.ts +241 -0
- package/src/server/claude-chat-management.test.ts +395 -0
- package/src/server/claude-chat-management.ts +251 -0
- package/src/server/claude-harness-stream.test.ts +273 -0
- package/src/server/claude-harness-stream.ts +158 -0
- package/src/server/claude-loop-orch-commands.test.ts +579 -0
- package/src/server/claude-loop-orch-commands.ts +466 -0
- package/src/server/claude-message-normalizer.test.ts +213 -0
- package/src/server/claude-message-normalizer.ts +436 -0
- package/src/server/claude-prompt-helpers.test.ts +248 -0
- package/src/server/claude-prompt-helpers.ts +139 -0
- package/src/server/claude-sdk-queue.test.ts +169 -0
- package/src/server/claude-sdk-queue.ts +82 -0
- package/src/server/claude-send-command.test.ts +414 -0
- package/src/server/claude-send-command.ts +420 -0
- package/src/server/claude-session-config-helpers.test.ts +279 -0
- package/src/server/claude-session-config-helpers.ts +151 -0
- package/src/server/claude-session-config.test.ts +250 -0
- package/src/server/claude-session-config.ts +159 -0
- package/src/server/claude-session-error-handler.test.ts +498 -0
- package/src/server/claude-session-error-handler.ts +361 -0
- package/src/server/claude-session-lifecycle.test.ts +669 -0
- package/src/server/claude-session-lifecycle.ts +283 -0
- package/src/server/claude-session-rebuild.test.ts +224 -0
- package/src/server/claude-session-rebuild.ts +101 -0
- package/src/server/claude-session-runner.test.ts +523 -0
- package/src/server/claude-session-runner.ts +521 -0
- package/src/server/claude-session-spawner.test.ts +401 -0
- package/src/server/claude-session-spawner.ts +401 -0
- package/src/server/claude-session-start.test.ts +205 -0
- package/src/server/claude-session-start.ts +277 -0
- package/src/server/claude-session-state-queries.test.ts +344 -0
- package/src/server/claude-session-state-queries.ts +160 -0
- package/src/server/claude-session-state.ts +104 -0
- package/src/server/claude-slash-commands.test.ts +277 -0
- package/src/server/claude-slash-commands.ts +234 -0
- package/src/server/claude-spawn-helpers.test.ts +221 -0
- package/src/server/claude-spawn-helpers.ts +179 -0
- package/src/server/claude-steer-log.test.ts +243 -0
- package/src/server/claude-steer-log.ts +58 -0
- package/src/server/claude-subagent-tool-response.test.ts +213 -0
- package/src/server/claude-subagent-tool-response.ts +165 -0
- package/src/server/claude-subagent-wiring.test.ts +263 -0
- package/src/server/claude-subagent-wiring.ts +324 -0
- package/src/server/claude-tool-respond.test.ts +321 -0
- package/src/server/claude-tool-respond.ts +142 -0
- package/src/server/claude-turn-runner.test.ts +310 -0
- package/src/server/claude-turn-runner.ts +228 -0
- package/src/server/claude-turn-starter.test.ts +266 -0
- package/src/server/claude-turn-starter.ts +566 -0
- package/src/server/claude-usage-math.test.ts +169 -0
- package/src/server/claude-usage-math.ts +132 -0
- package/src/server/diff-store-branch-ops.adapter.test.ts +332 -0
- package/src/server/diff-store-branch-ops.adapter.ts +602 -0
- package/src/server/diff-store-commit-ops.adapter.test.ts +358 -0
- package/src/server/diff-store-commit-ops.adapter.ts +258 -0
- package/src/server/diff-store-errors.ts +85 -0
- package/src/server/diff-store-file-ops.adapter.ts +212 -0
- package/src/server/diff-store-git-branch.adapter.ts +686 -0
- package/src/server/diff-store-io.adapter.ts +27 -0
- package/src/server/diff-store-parse.test.ts +212 -0
- package/src/server/diff-store-parse.ts +122 -0
- package/src/server/diff-store-state.ts +82 -0
- package/src/server/diff-store.ts +76 -1854
- package/src/server/event-store-apply.test.ts +280 -0
- package/src/server/event-store-apply.ts +132 -0
- package/src/server/event-store-chat-lifecycle.test.ts +408 -0
- package/src/server/event-store-chat-lifecycle.ts +441 -0
- package/src/server/event-store-entity-write.ts +356 -0
- package/src/server/event-store-helpers.test.ts +230 -0
- package/src/server/event-store-helpers.ts +235 -0
- package/src/server/event-store-init.ts +276 -0
- package/src/server/event-store-messages.adapter.test.ts +365 -0
- package/src/server/event-store-messages.adapter.ts +213 -0
- package/src/server/event-store-orch-subscription.ts +124 -0
- package/src/server/event-store-orch.test.ts +462 -0
- package/src/server/event-store-orch.ts +312 -0
- package/src/server/event-store-peripheral-events.adapter.test.ts +254 -0
- package/src/server/event-store-peripheral-events.adapter.ts +105 -0
- package/src/server/event-store-snapshot.test.ts +597 -0
- package/src/server/event-store-snapshot.ts +658 -0
- package/src/server/event-store-subagent.test.ts +408 -0
- package/src/server/event-store-subagent.ts +198 -0
- package/src/server/event-store-tool-requests.test.ts +210 -0
- package/src/server/event-store-tool-requests.ts +131 -0
- package/src/server/event-store-transcript-write.adapter.ts +285 -0
- package/src/server/event-store-write-ops.test.ts +554 -0
- package/src/server/event-store-write-ops.ts +529 -0
- package/src/server/event-store.test.ts +35 -0
- package/src/server/event-store.ts +251 -2284
- package/src/server/events.ts +3 -0
- package/src/server/harness-types.ts +15 -1
- package/src/server/oauth-errors.ts +19 -0
- package/src/server/read-models.ts +26 -1
- package/src/server/subagent-orchestrator.ts +3 -0
- package/src/server/ws-router-agent-ctrl.test.ts +187 -0
- package/src/server/ws-router-agent-ctrl.ts +137 -0
- package/src/server/ws-router-broadcast.ts +488 -0
- package/src/server/ws-router-chat.test.ts +461 -0
- package/src/server/ws-router-chat.ts +279 -0
- package/src/server/ws-router-defaults.test.ts +283 -0
- package/src/server/ws-router-defaults.ts +434 -0
- package/src/server/ws-router-diff.test.ts +167 -0
- package/src/server/ws-router-diff.ts +250 -0
- package/src/server/ws-router-envelope.ts +329 -0
- package/src/server/ws-router-misc.test.ts +422 -0
- package/src/server/ws-router-misc.ts +265 -0
- package/src/server/ws-router-orch.test.ts +190 -0
- package/src/server/ws-router-orch.ts +93 -0
- package/src/server/ws-router-project.test.ts +403 -0
- package/src/server/ws-router-project.ts +256 -0
- package/src/server/ws-router-push.test.ts +193 -0
- package/src/server/ws-router-push.ts +120 -0
- package/src/server/ws-router-settings.test.ts +173 -0
- package/src/server/ws-router-settings.ts +416 -0
- package/src/server/ws-router-skills.test.ts +194 -0
- package/src/server/ws-router-skills.ts +204 -0
- package/src/server/ws-router-utils.ts +224 -0
- package/src/server/ws-router.ts +339 -2254
- package/src/shared/app-settings-types.ts +365 -0
- package/src/shared/core-types.ts +27 -0
- package/src/shared/git-diff-types.ts +165 -0
- package/src/shared/loop-progress.test.ts +106 -0
- package/src/shared/loop-progress.ts +88 -0
- package/src/shared/mcp-types.ts +105 -0
- package/src/shared/provider-model-types.ts +437 -0
- package/src/shared/subagent-types.ts +198 -0
- package/src/shared/tool-call-types.ts +314 -0
- package/src/shared/transcript-types.ts +270 -0
- package/src/shared/types.ts +27 -1668
- package/dist/client/assets/channel-BMkhjiLI.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-C5Kg1XOD.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C5Kg1XOD.js +0 -1
- package/dist/client/assets/index-H6C-f2Vp.css +0 -32
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Ca2U11af.js +0 -1
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ws-router-project.ts
|
|
3
|
+
*
|
|
4
|
+
* WS command handlers for project management, session import, sidebar ordering,
|
|
5
|
+
* system utilities, and update management:
|
|
6
|
+
* system.ping, system.openExternal,
|
|
7
|
+
* update.check, update.install, update.reload,
|
|
8
|
+
* project.open, project.create, project.remove, project.setStar,
|
|
9
|
+
* project.readDiffPatch,
|
|
10
|
+
* sessions.importClaude,
|
|
11
|
+
* sidebar.reorderProjectGroups
|
|
12
|
+
*
|
|
13
|
+
* Extracted from ws-router.ts.
|
|
14
|
+
*/
|
|
15
|
+
import { PROTOCOL_VERSION } from "../shared/types"
|
|
16
|
+
import type { UpdateInstallResult, UpdateSnapshot } from "../shared/types"
|
|
17
|
+
import type { ClientCommand, ServerEnvelope } from "../shared/protocol"
|
|
18
|
+
import type { ImportClaudeSessionsResult } from "./claude-session-importer.adapter"
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Dep interfaces (duck-typed; avoids circular imports with ws-router.ts)
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
/** The subset of EventStore consumed by project WS commands. */
|
|
25
|
+
export interface ProjectStoreDep {
|
|
26
|
+
getProject(projectId: string): { id: string; localPath: string } | null | undefined
|
|
27
|
+
openProject(localPath: string, title?: string): Promise<{ id: string }>
|
|
28
|
+
removeProject(projectId: string): Promise<void>
|
|
29
|
+
setProjectStar(projectId: string, starred: boolean): Promise<void>
|
|
30
|
+
setSidebarProjectOrder(projectIds: string[]): Promise<void>
|
|
31
|
+
state: { projectIdsByPath: ReadonlyMap<string, string> }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The subset of UpdateManager consumed by update WS commands. */
|
|
35
|
+
export interface ProjectUpdateManagerDep {
|
|
36
|
+
checkForUpdates(opts?: { force?: boolean }): Promise<UpdateSnapshot>
|
|
37
|
+
installUpdate(opts?: { version?: string }): Promise<UpdateInstallResult>
|
|
38
|
+
forceReload(): Promise<UpdateInstallResult>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The subset of DiffStore consumed by project.readDiffPatch. */
|
|
42
|
+
export interface ProjectDiffStoreDep {
|
|
43
|
+
readPatch(args: { projectPath: string; path: string }): Promise<unknown>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Analytics reporter subset. */
|
|
47
|
+
export interface ProjectAnalyticsDep {
|
|
48
|
+
track(event: string): void
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Terminal manager subset needed to clean up on project removal. */
|
|
52
|
+
export interface ProjectTerminalsDep {
|
|
53
|
+
closeByCwd(cwd: string): void
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ProjectCommandDeps {
|
|
57
|
+
/** Project-related store methods. */
|
|
58
|
+
store: ProjectStoreDep
|
|
59
|
+
/** Update manager — optional; commands fail gracefully when absent. */
|
|
60
|
+
updateManager?: ProjectUpdateManagerDep | null
|
|
61
|
+
/** Diff store for readPatch. */
|
|
62
|
+
diffStore: ProjectDiffStoreDep
|
|
63
|
+
/** Analytics reporter. */
|
|
64
|
+
analytics: ProjectAnalyticsDep
|
|
65
|
+
/** Re-scans the workspace for new/removed projects. */
|
|
66
|
+
refreshDiscovery: () => Promise<unknown>
|
|
67
|
+
/** Ensures the target directory exists (creates it if needed). */
|
|
68
|
+
ensureProjectDirectory: (path: string) => Promise<void>
|
|
69
|
+
/** Normalizes / resolves a local path string. */
|
|
70
|
+
resolveLocalPath: (path: string) => string
|
|
71
|
+
/**
|
|
72
|
+
* Imports Claude sessions from disk.
|
|
73
|
+
* Caller pre-binds the store so the function signature is simple.
|
|
74
|
+
*/
|
|
75
|
+
importClaudeSessionsFn: () => Promise<ImportClaudeSessionsResult>
|
|
76
|
+
/**
|
|
77
|
+
* Opens an external application (editor, Finder, browser …).
|
|
78
|
+
* Caller pre-binds any internal deps.
|
|
79
|
+
*/
|
|
80
|
+
openExternalFn: (command: Extract<ClientCommand, { type: "system.openExternal" }>) => Promise<void>
|
|
81
|
+
/** Terminal manager — used to close terminals on project removal. */
|
|
82
|
+
terminals: ProjectTerminalsDep
|
|
83
|
+
/** Pre-bound to the current WebSocket; called to send an ack or push envelope. */
|
|
84
|
+
send: (envelope: ServerEnvelope) => void
|
|
85
|
+
/**
|
|
86
|
+
* Broadcast sidebar + project-list snapshots to all connected clients.
|
|
87
|
+
* Corresponds to `broadcastFilteredSnapshots({ includeSidebar: true })`.
|
|
88
|
+
*/
|
|
89
|
+
broadcastSidebar: () => Promise<void>
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Command dispatcher
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Handle one project / session / sidebar / system / update WS command.
|
|
98
|
+
*
|
|
99
|
+
* Returns `true` when the command was handled (caller should `return`).
|
|
100
|
+
* Returns `false` when the command type is outside this module's scope.
|
|
101
|
+
*/
|
|
102
|
+
export async function handleProjectCommand(
|
|
103
|
+
deps: ProjectCommandDeps,
|
|
104
|
+
command: ClientCommand,
|
|
105
|
+
id: string,
|
|
106
|
+
): Promise<boolean> {
|
|
107
|
+
const {
|
|
108
|
+
store,
|
|
109
|
+
updateManager,
|
|
110
|
+
diffStore,
|
|
111
|
+
analytics,
|
|
112
|
+
refreshDiscovery,
|
|
113
|
+
ensureProjectDirectory,
|
|
114
|
+
resolveLocalPath,
|
|
115
|
+
importClaudeSessionsFn,
|
|
116
|
+
openExternalFn,
|
|
117
|
+
terminals,
|
|
118
|
+
send,
|
|
119
|
+
broadcastSidebar,
|
|
120
|
+
} = deps
|
|
121
|
+
|
|
122
|
+
switch (command.type) {
|
|
123
|
+
// -----------------------------------------------------------------------
|
|
124
|
+
// system
|
|
125
|
+
// -----------------------------------------------------------------------
|
|
126
|
+
case "system.ping": {
|
|
127
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id })
|
|
128
|
+
return true
|
|
129
|
+
}
|
|
130
|
+
case "system.openExternal": {
|
|
131
|
+
await openExternalFn(command)
|
|
132
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id })
|
|
133
|
+
return true
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// -----------------------------------------------------------------------
|
|
137
|
+
// update
|
|
138
|
+
// -----------------------------------------------------------------------
|
|
139
|
+
case "update.check": {
|
|
140
|
+
const unavailableSnapshot: UpdateSnapshot = {
|
|
141
|
+
currentVersion: "unknown",
|
|
142
|
+
latestVersion: null,
|
|
143
|
+
status: "error",
|
|
144
|
+
updateAvailable: false,
|
|
145
|
+
lastCheckedAt: Date.now(),
|
|
146
|
+
error: "Update manager unavailable.",
|
|
147
|
+
installAction: "restart",
|
|
148
|
+
reloadRequestedAt: null,
|
|
149
|
+
}
|
|
150
|
+
const snapshot = updateManager
|
|
151
|
+
? await updateManager.checkForUpdates({ force: command.force })
|
|
152
|
+
: unavailableSnapshot
|
|
153
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id, result: snapshot })
|
|
154
|
+
return true
|
|
155
|
+
}
|
|
156
|
+
case "update.install": {
|
|
157
|
+
if (!updateManager) {
|
|
158
|
+
throw new Error("Update manager unavailable.")
|
|
159
|
+
}
|
|
160
|
+
const result = await updateManager.installUpdate({ version: command.version })
|
|
161
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
162
|
+
return true
|
|
163
|
+
}
|
|
164
|
+
case "update.reload": {
|
|
165
|
+
if (!updateManager) {
|
|
166
|
+
throw new Error("Update manager unavailable.")
|
|
167
|
+
}
|
|
168
|
+
const result = await updateManager.forceReload()
|
|
169
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
170
|
+
return true
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// -----------------------------------------------------------------------
|
|
174
|
+
// project
|
|
175
|
+
// -----------------------------------------------------------------------
|
|
176
|
+
case "project.open": {
|
|
177
|
+
await ensureProjectDirectory(command.localPath)
|
|
178
|
+
const normalizedPath = resolveLocalPath(command.localPath)
|
|
179
|
+
const existingProjectId = store.state.projectIdsByPath.get(normalizedPath)
|
|
180
|
+
const project = await store.openProject(command.localPath)
|
|
181
|
+
await refreshDiscovery()
|
|
182
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id, result: { projectId: project.id } })
|
|
183
|
+
if (!existingProjectId) {
|
|
184
|
+
analytics.track("project_opened")
|
|
185
|
+
}
|
|
186
|
+
return true
|
|
187
|
+
}
|
|
188
|
+
case "project.create": {
|
|
189
|
+
await ensureProjectDirectory(command.localPath)
|
|
190
|
+
const normalizedPath = resolveLocalPath(command.localPath)
|
|
191
|
+
const existingProjectId = store.state.projectIdsByPath.get(normalizedPath)
|
|
192
|
+
const project = await store.openProject(command.localPath, command.title)
|
|
193
|
+
await refreshDiscovery()
|
|
194
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id, result: { projectId: project.id } })
|
|
195
|
+
if (!existingProjectId) {
|
|
196
|
+
analytics.track("project_opened")
|
|
197
|
+
analytics.track("project_created")
|
|
198
|
+
}
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
201
|
+
case "project.remove": {
|
|
202
|
+
const project = store.getProject(command.projectId)
|
|
203
|
+
await store.removeProject(command.projectId)
|
|
204
|
+
if (project) {
|
|
205
|
+
terminals.closeByCwd(project.localPath)
|
|
206
|
+
}
|
|
207
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id })
|
|
208
|
+
analytics.track("project_removed")
|
|
209
|
+
return true
|
|
210
|
+
}
|
|
211
|
+
case "project.setStar": {
|
|
212
|
+
await store.setProjectStar(command.projectId, command.starred)
|
|
213
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id })
|
|
214
|
+
await broadcastSidebar()
|
|
215
|
+
return true
|
|
216
|
+
}
|
|
217
|
+
case "project.readDiffPatch": {
|
|
218
|
+
const project = store.getProject(command.projectId)
|
|
219
|
+
if (!project) {
|
|
220
|
+
throw new Error("Project not found")
|
|
221
|
+
}
|
|
222
|
+
const result = await diffStore.readPatch({
|
|
223
|
+
projectPath: project.localPath,
|
|
224
|
+
path: command.path,
|
|
225
|
+
})
|
|
226
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
227
|
+
return true
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// -----------------------------------------------------------------------
|
|
231
|
+
// sessions
|
|
232
|
+
// -----------------------------------------------------------------------
|
|
233
|
+
case "sessions.importClaude": {
|
|
234
|
+
const result = await importClaudeSessionsFn()
|
|
235
|
+
if (result.newProjects > 0) {
|
|
236
|
+
await refreshDiscovery()
|
|
237
|
+
}
|
|
238
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
239
|
+
await broadcastSidebar()
|
|
240
|
+
return true
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// -----------------------------------------------------------------------
|
|
244
|
+
// sidebar
|
|
245
|
+
// -----------------------------------------------------------------------
|
|
246
|
+
case "sidebar.reorderProjectGroups": {
|
|
247
|
+
await store.setSidebarProjectOrder(command.projectIds)
|
|
248
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id })
|
|
249
|
+
await broadcastSidebar()
|
|
250
|
+
return true
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
default:
|
|
254
|
+
return false
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test"
|
|
2
|
+
import type { PushCommandDeps, PushManagerDep } from "./ws-router-push"
|
|
3
|
+
import { handlePushCommand } from "./ws-router-push"
|
|
4
|
+
import type { ClientCommand } from "../shared/protocol"
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
function makePushManager(overrides: Partial<PushManagerDep> = {}): PushManagerDep {
|
|
11
|
+
return {
|
|
12
|
+
recordDeviceSeen: mock(async () => {}),
|
|
13
|
+
addSubscription: mock(async () => ({ id: "sub-1" })),
|
|
14
|
+
removeSubscription: mock(async () => {}),
|
|
15
|
+
sendTest: mock(async () => {}),
|
|
16
|
+
setProjectMute: mock(async () => {}),
|
|
17
|
+
setFocusedChat: mock(() => {}),
|
|
18
|
+
...overrides,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface TestDeps extends PushCommandDeps {
|
|
23
|
+
sent: unknown[]
|
|
24
|
+
broadcasts: Array<{ includePushConfig: boolean }>
|
|
25
|
+
_deviceId: string | null | undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function makeDeps(
|
|
29
|
+
pushManagerOverrides?: Partial<PushManagerDep>,
|
|
30
|
+
initialDeviceId?: string | null,
|
|
31
|
+
): TestDeps {
|
|
32
|
+
const sent: unknown[] = []
|
|
33
|
+
const broadcasts: Array<{ includePushConfig: boolean }> = []
|
|
34
|
+
let deviceId: string | null | undefined = initialDeviceId
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
pushManager: makePushManager(pushManagerOverrides),
|
|
38
|
+
getPushDeviceId: () => deviceId,
|
|
39
|
+
setPushDeviceId: (id) => { deviceId = id },
|
|
40
|
+
send: (envelope) => { sent.push(envelope) },
|
|
41
|
+
broadcastPushConfig: async () => { broadcasts.push({ includePushConfig: true }) },
|
|
42
|
+
sent,
|
|
43
|
+
broadcasts,
|
|
44
|
+
get _deviceId() { return deviceId },
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Unknown command
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
describe("handlePushCommand", () => {
|
|
53
|
+
test("returns false for a non-push command", async () => {
|
|
54
|
+
const deps = makeDeps()
|
|
55
|
+
const handled = await handlePushCommand(
|
|
56
|
+
deps,
|
|
57
|
+
{ type: "settings.readAppSettings" } as unknown as ClientCommand,
|
|
58
|
+
"r0",
|
|
59
|
+
)
|
|
60
|
+
expect(handled).toBe(false)
|
|
61
|
+
expect(deps.sent).toHaveLength(0)
|
|
62
|
+
expect(deps.broadcasts).toHaveLength(0)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// push.identifyDevice
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
test("push.identifyDevice — sets deviceId, calls recordDeviceSeen, broadcasts, acks", async () => {
|
|
70
|
+
const deps = makeDeps()
|
|
71
|
+
const cmd: ClientCommand = { type: "push.identifyDevice", pushDeviceId: "dev-1" }
|
|
72
|
+
const handled = await handlePushCommand(deps, cmd, "r1")
|
|
73
|
+
expect(handled).toBe(true)
|
|
74
|
+
expect(deps._deviceId).toBe("dev-1")
|
|
75
|
+
expect(deps.pushManager.recordDeviceSeen as ReturnType<typeof mock>).toHaveBeenCalledWith("dev-1")
|
|
76
|
+
expect(deps.broadcasts).toHaveLength(1)
|
|
77
|
+
expect(deps.broadcasts[0]).toEqual({ includePushConfig: true })
|
|
78
|
+
expect((deps.sent[0] as { type: string }).type).toBe("ack")
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test("push.identifyDevice with null — sets null, skips recordDeviceSeen, no broadcast", async () => {
|
|
82
|
+
const deps = makeDeps()
|
|
83
|
+
const cmd: ClientCommand = { type: "push.identifyDevice", pushDeviceId: null }
|
|
84
|
+
const handled = await handlePushCommand(deps, cmd, "r2")
|
|
85
|
+
expect(handled).toBe(true)
|
|
86
|
+
expect(deps._deviceId).toBeNull()
|
|
87
|
+
expect(deps.pushManager.recordDeviceSeen as ReturnType<typeof mock>).not.toHaveBeenCalled()
|
|
88
|
+
expect(deps.broadcasts).toHaveLength(0)
|
|
89
|
+
expect((deps.sent[0] as { type: string }).type).toBe("ack")
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// push.subscribe
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
test("push.subscribe — calls addSubscription, sets deviceId from result, broadcasts, acks with result", async () => {
|
|
97
|
+
const deps = makeDeps({ addSubscription: mock(async () => ({ id: "sub-new" })) })
|
|
98
|
+
const cmd: ClientCommand = {
|
|
99
|
+
type: "push.subscribe",
|
|
100
|
+
subscription: { endpoint: "https://example.com/push", keys: { p256dh: "abc", auth: "def" } },
|
|
101
|
+
label: "Test device",
|
|
102
|
+
userAgent: "Bun/1.0",
|
|
103
|
+
}
|
|
104
|
+
const handled = await handlePushCommand(deps, cmd, "r3")
|
|
105
|
+
expect(handled).toBe(true)
|
|
106
|
+
expect(deps._deviceId).toBe("sub-new")
|
|
107
|
+
expect(deps.broadcasts).toHaveLength(1)
|
|
108
|
+
const ack = deps.sent[0] as { type: string; result: unknown }
|
|
109
|
+
expect(ack.type).toBe("ack")
|
|
110
|
+
expect(ack.result).toEqual({ id: "sub-new" })
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// push.unsubscribe
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
test("push.unsubscribe — calls removeSubscription, clears deviceId when it matches, broadcasts, acks", async () => {
|
|
118
|
+
const deps = makeDeps(undefined, "dev-1")
|
|
119
|
+
const cmd: ClientCommand = { type: "push.unsubscribe", pushDeviceId: "dev-1" }
|
|
120
|
+
const handled = await handlePushCommand(deps, cmd, "r4")
|
|
121
|
+
expect(handled).toBe(true)
|
|
122
|
+
expect(deps.pushManager.removeSubscription as ReturnType<typeof mock>).toHaveBeenCalledWith("dev-1", "user_revoked")
|
|
123
|
+
expect(deps._deviceId).toBeNull()
|
|
124
|
+
expect(deps.broadcasts).toHaveLength(1)
|
|
125
|
+
expect((deps.sent[0] as { type: string }).type).toBe("ack")
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test("push.unsubscribe — does not clear deviceId when different device is unsubscribed", async () => {
|
|
129
|
+
const deps = makeDeps(undefined, "dev-1")
|
|
130
|
+
const cmd: ClientCommand = { type: "push.unsubscribe", pushDeviceId: "dev-2" }
|
|
131
|
+
await handlePushCommand(deps, cmd, "r5")
|
|
132
|
+
expect(deps._deviceId).toBe("dev-1")
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// push.test
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
test("push.test — calls sendTest with current deviceId and acks", async () => {
|
|
140
|
+
const deps = makeDeps(undefined, "dev-1")
|
|
141
|
+
const cmd: ClientCommand = { type: "push.test" }
|
|
142
|
+
const handled = await handlePushCommand(deps, cmd, "r6")
|
|
143
|
+
expect(handled).toBe(true)
|
|
144
|
+
expect(deps.pushManager.sendTest as ReturnType<typeof mock>).toHaveBeenCalledWith("dev-1")
|
|
145
|
+
expect((deps.sent[0] as { type: string }).type).toBe("ack")
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test("push.test — skips sendTest when no deviceId, still acks", async () => {
|
|
149
|
+
const deps = makeDeps()
|
|
150
|
+
const cmd: ClientCommand = { type: "push.test" }
|
|
151
|
+
const handled = await handlePushCommand(deps, cmd, "r7")
|
|
152
|
+
expect(handled).toBe(true)
|
|
153
|
+
expect(deps.pushManager.sendTest as ReturnType<typeof mock>).not.toHaveBeenCalled()
|
|
154
|
+
expect((deps.sent[0] as { type: string }).type).toBe("ack")
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// push.setProjectMute
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
test("push.setProjectMute — calls setProjectMute, broadcasts, acks", async () => {
|
|
162
|
+
const deps = makeDeps()
|
|
163
|
+
const cmd: ClientCommand = { type: "push.setProjectMute", localPath: "/projects/foo", muted: true }
|
|
164
|
+
const handled = await handlePushCommand(deps, cmd, "r8")
|
|
165
|
+
expect(handled).toBe(true)
|
|
166
|
+
expect(deps.pushManager.setProjectMute as ReturnType<typeof mock>).toHaveBeenCalledWith("/projects/foo", true)
|
|
167
|
+
expect(deps.broadcasts).toHaveLength(1)
|
|
168
|
+
expect(deps.broadcasts[0]).toEqual({ includePushConfig: true })
|
|
169
|
+
expect((deps.sent[0] as { type: string }).type).toBe("ack")
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// push.setFocusedChat
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
test("push.setFocusedChat — calls setFocusedChat when deviceId is set, acks", async () => {
|
|
177
|
+
const deps = makeDeps(undefined, "dev-1")
|
|
178
|
+
const cmd: ClientCommand = { type: "push.setFocusedChat", chatId: "chat-42" }
|
|
179
|
+
const handled = await handlePushCommand(deps, cmd, "r9")
|
|
180
|
+
expect(handled).toBe(true)
|
|
181
|
+
expect(deps.pushManager.setFocusedChat as ReturnType<typeof mock>).toHaveBeenCalledWith("dev-1", "chat-42")
|
|
182
|
+
expect((deps.sent[0] as { type: string }).type).toBe("ack")
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test("push.setFocusedChat — skips setFocusedChat when no deviceId, still acks", async () => {
|
|
186
|
+
const deps = makeDeps()
|
|
187
|
+
const cmd: ClientCommand = { type: "push.setFocusedChat", chatId: "chat-42" }
|
|
188
|
+
const handled = await handlePushCommand(deps, cmd, "r10")
|
|
189
|
+
expect(handled).toBe(true)
|
|
190
|
+
expect(deps.pushManager.setFocusedChat as ReturnType<typeof mock>).not.toHaveBeenCalled()
|
|
191
|
+
expect((deps.sent[0] as { type: string }).type).toBe("ack")
|
|
192
|
+
})
|
|
193
|
+
})
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ws-router-push.ts
|
|
3
|
+
*
|
|
4
|
+
* WS command handlers for push-notification lifecycle operations:
|
|
5
|
+
* push.identifyDevice, push.subscribe, push.unsubscribe,
|
|
6
|
+
* push.test, push.setProjectMute, push.setFocusedChat
|
|
7
|
+
*
|
|
8
|
+
* Extracted from ws-router.ts.
|
|
9
|
+
*/
|
|
10
|
+
import { PROTOCOL_VERSION } from "../shared/types"
|
|
11
|
+
import type { PushSubscribeRequestPayload } from "../shared/types"
|
|
12
|
+
import type { ClientCommand, ServerEnvelope } from "../shared/protocol"
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Dep interfaces (duck-typed; avoids circular imports with ws-router.ts)
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/** The subset of PushManager methods consumed by push WS commands. */
|
|
19
|
+
export interface PushManagerDep {
|
|
20
|
+
recordDeviceSeen(id: string): Promise<void>
|
|
21
|
+
addSubscription(args: {
|
|
22
|
+
subscription: PushSubscribeRequestPayload
|
|
23
|
+
label: string
|
|
24
|
+
userAgent: string
|
|
25
|
+
}): Promise<{ id: string }>
|
|
26
|
+
removeSubscription(id: string, reason: "user_revoked" | "expired" | "replaced"): Promise<void>
|
|
27
|
+
sendTest(id: string): Promise<void>
|
|
28
|
+
setProjectMute(localPath: string, muted: boolean): Promise<void>
|
|
29
|
+
setFocusedChat(deviceId: string, chatId: string | null): void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface PushCommandDeps {
|
|
33
|
+
/** Push manager for device/subscription operations. */
|
|
34
|
+
pushManager: PushManagerDep
|
|
35
|
+
/** Read the current connection's push device id. */
|
|
36
|
+
getPushDeviceId: () => string | null | undefined
|
|
37
|
+
/** Persist a new device id on the current connection (or null to clear). */
|
|
38
|
+
setPushDeviceId: (id: string | null) => void
|
|
39
|
+
/** Pre-bound to the current WebSocket; called to send an ack envelope. */
|
|
40
|
+
send: (envelope: ServerEnvelope) => void
|
|
41
|
+
/**
|
|
42
|
+
* Broadcast the updated push-config snapshot to all connected clients.
|
|
43
|
+
* Called after any operation that changes subscription or mute state.
|
|
44
|
+
*/
|
|
45
|
+
broadcastPushConfig: () => Promise<void>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Command dispatcher
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Handle one push WS command.
|
|
54
|
+
*
|
|
55
|
+
* Returns `true` when the command was handled (caller should `return`).
|
|
56
|
+
* Returns `false` when the command type is outside this module's scope.
|
|
57
|
+
*/
|
|
58
|
+
export async function handlePushCommand(
|
|
59
|
+
deps: PushCommandDeps,
|
|
60
|
+
command: ClientCommand,
|
|
61
|
+
id: string,
|
|
62
|
+
): Promise<boolean> {
|
|
63
|
+
const { pushManager, getPushDeviceId, setPushDeviceId, send, broadcastPushConfig } = deps
|
|
64
|
+
|
|
65
|
+
switch (command.type) {
|
|
66
|
+
case "push.identifyDevice": {
|
|
67
|
+
setPushDeviceId(command.pushDeviceId)
|
|
68
|
+
if (command.pushDeviceId) {
|
|
69
|
+
await pushManager.recordDeviceSeen(command.pushDeviceId)
|
|
70
|
+
await broadcastPushConfig()
|
|
71
|
+
}
|
|
72
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id })
|
|
73
|
+
return true
|
|
74
|
+
}
|
|
75
|
+
case "push.subscribe": {
|
|
76
|
+
const result = await pushManager.addSubscription({
|
|
77
|
+
subscription: command.subscription,
|
|
78
|
+
label: command.label,
|
|
79
|
+
userAgent: command.userAgent,
|
|
80
|
+
})
|
|
81
|
+
setPushDeviceId(result.id)
|
|
82
|
+
await broadcastPushConfig()
|
|
83
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
case "push.unsubscribe": {
|
|
87
|
+
await pushManager.removeSubscription(command.pushDeviceId, "user_revoked")
|
|
88
|
+
if (getPushDeviceId() === command.pushDeviceId) {
|
|
89
|
+
setPushDeviceId(null)
|
|
90
|
+
}
|
|
91
|
+
await broadcastPushConfig()
|
|
92
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id })
|
|
93
|
+
return true
|
|
94
|
+
}
|
|
95
|
+
case "push.test": {
|
|
96
|
+
const deviceId = getPushDeviceId()
|
|
97
|
+
if (deviceId) {
|
|
98
|
+
await pushManager.sendTest(deviceId)
|
|
99
|
+
}
|
|
100
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id })
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
case "push.setProjectMute": {
|
|
104
|
+
await pushManager.setProjectMute(command.localPath, command.muted)
|
|
105
|
+
await broadcastPushConfig()
|
|
106
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id })
|
|
107
|
+
return true
|
|
108
|
+
}
|
|
109
|
+
case "push.setFocusedChat": {
|
|
110
|
+
const deviceId = getPushDeviceId()
|
|
111
|
+
if (deviceId) {
|
|
112
|
+
pushManager.setFocusedChat(deviceId, command.chatId)
|
|
113
|
+
}
|
|
114
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id })
|
|
115
|
+
return true
|
|
116
|
+
}
|
|
117
|
+
default:
|
|
118
|
+
return false
|
|
119
|
+
}
|
|
120
|
+
}
|