@cuongtran001/kanna 1.3.0 → 1.4.0
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-CAysfmX5.js → arc-BHj370W7.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BKqRYsm4.js → architectureDiagram-3BPJPVTR-1G88q1PY.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-Ce7Eorec.js → blockDiagram-GPEHLZMM-D0L3Co5v.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-CJ77zauH.js → c4Diagram-AAUBKEIU-CQtERl3H.js} +1 -1
- package/dist/client/assets/channel-BP9IMEvd.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-Rk_3zLxJ.js → chunk-2J33WTMH-DxOwt09f.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-DPIz6Nvf.js → chunk-4BX2VUAB-Wry66y9R.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-D-TkbGh2.js → chunk-55IACEB6-D6p-Hi4x.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-B4NOSIZV.js → chunk-727SXJPM-CRQZt23N.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-Cm_TGLYm.js → chunk-AQP2D5EJ-BzgTKOM9.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-Bu45BUVh.js → chunk-FMBD7UC4-CKfD-91C.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-C1Bw8_gg.js → chunk-ND2GUHAM-a71B27wc.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-DueG15o0.js → chunk-QZHKN3VN-vv9gJwx0.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK--W6Wt7FF.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2--W6Wt7FF.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-YgpMurXf.js → cose-bilkent-S5V4N54A-Cu-dCCaI.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-Dop7Oq67.js → dagre-BM42HDAG-D8coOWLK.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-unMfBEJH.js → diagram-2AECGRRQ-NIwZ65jE.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-D9EJ9qMI.js → diagram-5GNKFQAL-d46aE_le.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-C5IykOIR.js → diagram-KO2AKTUF-xdm2UxWc.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DJAuPngz.js → diagram-LMA3HP47-hDbLwC5K.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-B3wHFtS-.js → diagram-OG6HWLK6-MHUGG7ki.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-Dm7kt9o5.js → erDiagram-TEJ5UH35-Ro6n9X70.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-s9Lds3ny.js → flowDiagram-I6XJVG4X-DOZqvRMu.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-CRQNYdAf.js → ganttDiagram-6RSMTGT7-Bn2NXYIy.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DhYjK4Jb.js → gitGraphDiagram-PVQCEYII-DK44bfbC.js} +1 -1
- package/dist/client/assets/{index-C0U2kwuB.js → index-CWJpHyQy.js} +115 -115
- package/dist/client/assets/{index-CLsOWXff.css → index-Cyzd4zZx.css} +1 -1
- package/dist/client/assets/{index-CbSY9HfS.js → index-DU56d4oC.js} +1 -1
- package/dist/client/assets/{infoDiagram-5YYISTIA-CRKJ0PSH.js → infoDiagram-5YYISTIA-C2tW_Dwj.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CEAkB0IY.js → ishikawaDiagram-YF4QCWOH-C__4Z1wz.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-CyOK5_DG.js → journeyDiagram-JHISSGLW-Bbn5EawR.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-BfKjiJSo.js → kanban-definition-UN3LZRKU-RgS5IRWl.js} +1 -1
- package/dist/client/assets/{linear-DLR6miy3.js → linear-DfI0qBO_.js} +1 -1
- package/dist/client/assets/{mermaid.core-D7AdC_lE.js → mermaid.core-BiN10VHc.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-zYyJtDSa.js → mindmap-definition-RKZ34NQL-BQhibhh3.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-BgP8PFlo.js → pieDiagram-4H26LBE5-BEfLRn7a.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Di8h7J96.js → quadrantDiagram-W4KKPZXB-ba641Y2x.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-DuTzFl3R.js → requirementDiagram-4Y6WPE33-BGtl-tVO.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DBRoMm_T.js → sankeyDiagram-5OEKKPKP-DyziaSFr.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Bmb-1HUN.js → sequenceDiagram-3UESZ5HK-BfYBAYga.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-fnv5ayn-.js → stateDiagram-AJRCARHV-BfUfmLYA.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CdGcgYEq.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-ByWRdpQX.js → timeline-definition-PNZ67QCA-T6z7dEFl.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN--2WGpVXW.js → vennDiagram-CIIHVFJN-BAwQbFFd.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-24zlnzxK.js → wardley-L42UT6IY-DF1cRKsP.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Cllvqb3K.js → wardleyDiagram-YWT4CUSO-BRtAZ8za.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-ZcdC6nPE.js → xychartDiagram-2RQKCTM6-Brb-q76h.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.test.ts +22 -6
- package/src/server/agent.ts +35 -4987
- package/src/server/chat-op-log.test.ts +63 -0
- package/src/server/chat-op-log.ts +71 -0
- package/src/server/chat-ops-diff.test.ts +120 -0
- package/src/server/chat-ops-diff.ts +97 -0
- package/src/server/chat-ops-parity.test.ts +140 -0
- 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 +532 -0
- package/src/server/event-store-messages.adapter.ts +455 -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 +291 -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 +75 -0
- package/src/server/event-store.ts +257 -2285
- package/src/server/events.ts +1 -1
- package/src/server/harness-types.ts +15 -1
- package/src/server/oauth-errors.ts +19 -0
- package/src/server/storage/backend.ts +7 -0
- package/src/server/storage/fs-storage.adapter.ts +21 -1
- package/src/server/storage/in-memory-storage.ts +12 -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 +560 -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 +360 -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 +238 -0
- package/src/server/ws-router.test.ts +90 -0
- package/src/server/ws-router.ts +340 -2254
- package/src/shared/app-settings-types.ts +365 -0
- package/src/shared/chat-ops.test.ts +138 -0
- package/src/shared/chat-ops.ts +71 -0
- package/src/shared/core-types.ts +27 -0
- package/src/shared/git-diff-types.ts +165 -0
- package/src/shared/mcp-types.ts +105 -0
- package/src/shared/protocol.ts +3 -2
- 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 +31 -1711
- package/dist/client/assets/channel-KQhRwDzJ.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-ChxT1GNq.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-ChxT1GNq.js +0 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-D4X-ZnLV.js +0 -1
package/src/server/events.ts
CHANGED
|
@@ -649,6 +649,6 @@ export function createEmptyState(): StoreState {
|
|
|
649
649
|
}
|
|
650
650
|
}
|
|
651
651
|
|
|
652
|
-
export function cloneTranscriptEntries(entries: TranscriptEntry[]): TranscriptEntry[] {
|
|
652
|
+
export function cloneTranscriptEntries(entries: readonly TranscriptEntry[]): TranscriptEntry[] {
|
|
653
653
|
return entries.map((entry) => ({ ...entry }))
|
|
654
654
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AccountInfo, AgentProvider, NormalizedToolCall, TranscriptEntry } from "../shared/types"
|
|
1
|
+
import type { AccountInfo, AgentProvider, NormalizedToolCall, SlashCommand, TranscriptEntry } from "../shared/types"
|
|
2
2
|
|
|
3
3
|
export interface HarnessEvent {
|
|
4
4
|
type: "transcript" | "session_token" | "rate_limit"
|
|
@@ -18,3 +18,17 @@ export interface HarnessTurn {
|
|
|
18
18
|
interrupt: () => Promise<void>
|
|
19
19
|
close: () => void
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
export interface ClaudeSessionHandle {
|
|
23
|
+
provider: "claude"
|
|
24
|
+
stream: AsyncIterable<HarnessEvent>
|
|
25
|
+
getAccountInfo?: () => Promise<AccountInfo | null>
|
|
26
|
+
interrupt: () => Promise<void>
|
|
27
|
+
close: () => void
|
|
28
|
+
sendPrompt: (content: string) => Promise<void>
|
|
29
|
+
setModel: (model: string) => Promise<void>
|
|
30
|
+
setPermissionMode: (planMode: boolean) => Promise<void>
|
|
31
|
+
getSupportedCommands: () => Promise<SlashCommand[]>
|
|
32
|
+
/** Present only for keep-alive channel-delivery sessions; drives turn 2+. */
|
|
33
|
+
pushChannelPrompt?: (text: string) => Promise<void>
|
|
34
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth-pool error types shared between agent.ts and turn-spawning modules.
|
|
3
|
+
* Kept in a standalone file to avoid circular imports.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Thrown by Claude spawn paths when the OAuth pool has tokens but every one
|
|
8
|
+
* is currently unusable (rate-limited, errored, disabled, or reserved by
|
|
9
|
+
* another chat). `startTurnForChat` catches this and persists `message` as a
|
|
10
|
+
* `result` transcript entry instead of letting it surface as an ephemeral
|
|
11
|
+
* commandError that gets wiped by the next chat snapshot tick.
|
|
12
|
+
*/
|
|
13
|
+
export class OAuthPoolUnavailableError extends Error {
|
|
14
|
+
readonly kind = "oauth_pool_unavailable" as const
|
|
15
|
+
constructor(message: string) {
|
|
16
|
+
super(message)
|
|
17
|
+
this.name = "OAuthPoolUnavailableError"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -7,6 +7,13 @@ export interface StorageBackend {
|
|
|
7
7
|
size(path: string): Promise<number>
|
|
8
8
|
readText(path: string): Promise<string>
|
|
9
9
|
readTextSync(path: string): string
|
|
10
|
+
/** Synchronous file size in bytes; 0 if missing. Optional (tail-read fast path). */
|
|
11
|
+
sizeSync?(path: string): number
|
|
12
|
+
/**
|
|
13
|
+
* Synchronous byte-range read `[start, endExclusive)`. Optional — callers
|
|
14
|
+
* MUST fall back to full reads when absent (tail-read fast path only).
|
|
15
|
+
*/
|
|
16
|
+
readSliceSync?(path: string, start: number, endExclusive: number): Uint8Array
|
|
10
17
|
writeText(path: string, content: string): Promise<void>
|
|
11
18
|
appendText(path: string, content: string): Promise<void>
|
|
12
19
|
rename(from: string, to: string): Promise<void>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { appendFile, mkdir, rename, rm } from "node:fs/promises"
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs"
|
|
2
|
+
import { closeSync, existsSync, openSync, readFileSync, readSync, statSync } from "node:fs"
|
|
3
3
|
import type { StorageBackend } from "./backend"
|
|
4
4
|
|
|
5
5
|
export class FsStorageBackend implements StorageBackend {
|
|
@@ -27,6 +27,26 @@ export class FsStorageBackend implements StorageBackend {
|
|
|
27
27
|
return readFileSync(path, "utf8")
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
sizeSync(path: string): number {
|
|
31
|
+
try {
|
|
32
|
+
return statSync(path).size
|
|
33
|
+
} catch {
|
|
34
|
+
return 0
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
readSliceSync(path: string, start: number, endExclusive: number): Uint8Array {
|
|
39
|
+
const length = Math.max(0, endExclusive - start)
|
|
40
|
+
const buffer = Buffer.alloc(length)
|
|
41
|
+
const fd = openSync(path, "r")
|
|
42
|
+
try {
|
|
43
|
+
const bytesRead = readSync(fd, buffer, 0, length, start)
|
|
44
|
+
return buffer.subarray(0, bytesRead)
|
|
45
|
+
} finally {
|
|
46
|
+
closeSync(fd)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
30
50
|
async writeText(path: string, content: string): Promise<void> {
|
|
31
51
|
await Bun.write(path, content)
|
|
32
52
|
}
|
|
@@ -46,6 +46,18 @@ export class InMemoryStorageBackend implements StorageBackend {
|
|
|
46
46
|
return content
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
sizeSync(p: string): number {
|
|
50
|
+
const content = this.files.get(normalize(p))
|
|
51
|
+
if (content === undefined) return 0
|
|
52
|
+
return Buffer.byteLength(content, "utf8")
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
readSliceSync(p: string, start: number, endExclusive: number): Uint8Array {
|
|
56
|
+
const content = this.files.get(normalize(p))
|
|
57
|
+
if (content === undefined) return new Uint8Array(0)
|
|
58
|
+
return Uint8Array.prototype.slice.call(Buffer.from(content, "utf8"), start, endExclusive)
|
|
59
|
+
}
|
|
60
|
+
|
|
49
61
|
async writeText(p: string, content: string): Promise<void> {
|
|
50
62
|
const norm = normalize(p)
|
|
51
63
|
this.files.set(norm, content)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test"
|
|
2
|
+
import type { AgentCtrlCommandDeps, AgentCtrlAgentDep, TunnelGatewayDep } from "./ws-router-agent-ctrl"
|
|
3
|
+
import { handleAgentCtrlCommand } from "./ws-router-agent-ctrl"
|
|
4
|
+
import type { ClientCommand } from "../shared/protocol"
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
function makeAgent(overrides: Partial<AgentCtrlAgentDep> = {}): AgentCtrlAgentDep {
|
|
11
|
+
return {
|
|
12
|
+
acceptAutoContinue: mock(async () => {}),
|
|
13
|
+
rescheduleAutoContinue: mock(async () => {}),
|
|
14
|
+
cancelAutoContinue: mock(async () => {}),
|
|
15
|
+
cancel: mock(async () => {}),
|
|
16
|
+
...overrides,
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function makeTunnel(overrides: Partial<TunnelGatewayDep> = {}): TunnelGatewayDep {
|
|
21
|
+
return {
|
|
22
|
+
accept: mock(async () => {}),
|
|
23
|
+
stop: mock(async () => {}),
|
|
24
|
+
retry: mock(async () => {}),
|
|
25
|
+
...overrides,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function makeDeps(
|
|
30
|
+
agentOverrides?: Partial<AgentCtrlAgentDep>,
|
|
31
|
+
tunnel?: TunnelGatewayDep | undefined,
|
|
32
|
+
killPty?: AgentCtrlCommandDeps["killPtyInstance"],
|
|
33
|
+
): AgentCtrlCommandDeps & { sent: unknown[]; broadcasts: string[] } {
|
|
34
|
+
const sent: unknown[] = []
|
|
35
|
+
const broadcasts: string[] = []
|
|
36
|
+
return {
|
|
37
|
+
agent: makeAgent(agentOverrides),
|
|
38
|
+
tunnelGateway: tunnel,
|
|
39
|
+
killPtyInstance: killPty,
|
|
40
|
+
send: (envelope) => { sent.push(envelope) },
|
|
41
|
+
broadcastChatAndSidebar: async (chatId) => { broadcasts.push(chatId) },
|
|
42
|
+
sent,
|
|
43
|
+
broadcasts,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Unrecognized command
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
describe("handleAgentCtrlCommand", () => {
|
|
52
|
+
test("returns false for a non-agent-ctrl command", async () => {
|
|
53
|
+
const deps = makeDeps()
|
|
54
|
+
const handled = await handleAgentCtrlCommand(
|
|
55
|
+
deps,
|
|
56
|
+
{ type: "settings.readAppSettings" } as unknown as ClientCommand,
|
|
57
|
+
"r0",
|
|
58
|
+
)
|
|
59
|
+
expect(handled).toBe(false)
|
|
60
|
+
expect(deps.sent).toHaveLength(0)
|
|
61
|
+
expect(deps.broadcasts).toHaveLength(0)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// autoContinue
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
test("autoContinue.accept — calls agent, acks, and broadcasts", async () => {
|
|
69
|
+
const deps = makeDeps()
|
|
70
|
+
const cmd: ClientCommand = { type: "autoContinue.accept", chatId: "c-1", scheduleId: "s-1", scheduledAt: 1234 }
|
|
71
|
+
const handled = await handleAgentCtrlCommand(deps, cmd, "r1")
|
|
72
|
+
expect(handled).toBe(true)
|
|
73
|
+
expect((deps.agent.acceptAutoContinue as ReturnType<typeof mock>)).toHaveBeenCalledWith("c-1", "s-1", 1234)
|
|
74
|
+
expect(deps.sent).toHaveLength(1)
|
|
75
|
+
expect((deps.sent[0] as { type: string }).type).toBe("ack")
|
|
76
|
+
expect(deps.broadcasts).toEqual(["c-1"])
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test("autoContinue.reschedule — calls agent, acks, and broadcasts", async () => {
|
|
80
|
+
const deps = makeDeps()
|
|
81
|
+
const cmd: ClientCommand = { type: "autoContinue.reschedule", chatId: "c-2", scheduleId: "s-2", scheduledAt: 5678 }
|
|
82
|
+
const handled = await handleAgentCtrlCommand(deps, cmd, "r2")
|
|
83
|
+
expect(handled).toBe(true)
|
|
84
|
+
expect((deps.agent.rescheduleAutoContinue as ReturnType<typeof mock>)).toHaveBeenCalledWith("c-2", "s-2", 5678)
|
|
85
|
+
expect(deps.sent).toHaveLength(1)
|
|
86
|
+
expect(deps.broadcasts).toEqual(["c-2"])
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test("autoContinue.cancel — calls agent with reason 'user', acks, and broadcasts", async () => {
|
|
90
|
+
const deps = makeDeps()
|
|
91
|
+
const cmd: ClientCommand = { type: "autoContinue.cancel", chatId: "c-3", scheduleId: "s-3" }
|
|
92
|
+
const handled = await handleAgentCtrlCommand(deps, cmd, "r3")
|
|
93
|
+
expect(handled).toBe(true)
|
|
94
|
+
expect((deps.agent.cancelAutoContinue as ReturnType<typeof mock>)).toHaveBeenCalledWith("c-3", "s-3", "user")
|
|
95
|
+
expect(deps.sent).toHaveLength(1)
|
|
96
|
+
expect(deps.broadcasts).toEqual(["c-3"])
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// tunnel
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
test("tunnel.accept — calls tunnelGateway, acks, and broadcasts", async () => {
|
|
104
|
+
const tunnel = makeTunnel()
|
|
105
|
+
const deps = makeDeps(undefined, tunnel)
|
|
106
|
+
const cmd: ClientCommand = { type: "tunnel.accept", chatId: "c-4", tunnelId: "t-1" }
|
|
107
|
+
const handled = await handleAgentCtrlCommand(deps, cmd, "r4")
|
|
108
|
+
expect(handled).toBe(true)
|
|
109
|
+
expect((tunnel.accept as ReturnType<typeof mock>)).toHaveBeenCalledWith("c-4", "t-1")
|
|
110
|
+
expect(deps.sent).toHaveLength(1)
|
|
111
|
+
expect(deps.broadcasts).toEqual(["c-4"])
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test("tunnel.accept — still acks and broadcasts when tunnelGateway is absent", async () => {
|
|
115
|
+
const deps = makeDeps(undefined, undefined)
|
|
116
|
+
const cmd: ClientCommand = { type: "tunnel.accept", chatId: "c-5", tunnelId: "t-2" }
|
|
117
|
+
const handled = await handleAgentCtrlCommand(deps, cmd, "r5")
|
|
118
|
+
expect(handled).toBe(true)
|
|
119
|
+
expect(deps.sent).toHaveLength(1)
|
|
120
|
+
expect(deps.broadcasts).toEqual(["c-5"])
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test("tunnel.stop — calls tunnelGateway, acks, and broadcasts", async () => {
|
|
124
|
+
const tunnel = makeTunnel()
|
|
125
|
+
const deps = makeDeps(undefined, tunnel)
|
|
126
|
+
const cmd: ClientCommand = { type: "tunnel.stop", chatId: "c-6", tunnelId: "t-3" }
|
|
127
|
+
const handled = await handleAgentCtrlCommand(deps, cmd, "r6")
|
|
128
|
+
expect(handled).toBe(true)
|
|
129
|
+
expect((tunnel.stop as ReturnType<typeof mock>)).toHaveBeenCalledWith("c-6", "t-3")
|
|
130
|
+
expect(deps.broadcasts).toEqual(["c-6"])
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test("tunnel.retry — calls tunnelGateway, acks, and broadcasts", async () => {
|
|
134
|
+
const tunnel = makeTunnel()
|
|
135
|
+
const deps = makeDeps(undefined, tunnel)
|
|
136
|
+
const cmd: ClientCommand = { type: "tunnel.retry", chatId: "c-7", tunnelId: "t-4" }
|
|
137
|
+
const handled = await handleAgentCtrlCommand(deps, cmd, "r7")
|
|
138
|
+
expect(handled).toBe(true)
|
|
139
|
+
expect((tunnel.retry as ReturnType<typeof mock>)).toHaveBeenCalledWith("c-7", "t-4")
|
|
140
|
+
expect(deps.broadcasts).toEqual(["c-7"])
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// pty
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
test("pty.cancel — acks {ok:true} on success", async () => {
|
|
148
|
+
const deps = makeDeps()
|
|
149
|
+
const cmd: ClientCommand = { type: "pty.cancel", chatId: "c-8" }
|
|
150
|
+
const handled = await handleAgentCtrlCommand(deps, cmd, "r8")
|
|
151
|
+
expect(handled).toBe(true)
|
|
152
|
+
expect((deps.agent.cancel as ReturnType<typeof mock>)).toHaveBeenCalledWith("c-8")
|
|
153
|
+
const ack = deps.sent[0] as { result: { ok: boolean } }
|
|
154
|
+
expect(ack.result.ok).toBe(true)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
test("pty.cancel — acks {ok:false} when agent.cancel throws", async () => {
|
|
158
|
+
const deps = makeDeps({ cancel: mock(async () => { throw new Error("boom") }) })
|
|
159
|
+
const cmd: ClientCommand = { type: "pty.cancel", chatId: "c-9" }
|
|
160
|
+
const handled = await handleAgentCtrlCommand(deps, cmd, "r9")
|
|
161
|
+
expect(handled).toBe(true)
|
|
162
|
+
const ack = deps.sent[0] as { result: { ok: boolean; error: string } }
|
|
163
|
+
expect(ack.result.ok).toBe(false)
|
|
164
|
+
expect(ack.result.error).toBe("boom")
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test("pty.kill — returns {ok:false} error when killPtyInstance is absent", async () => {
|
|
168
|
+
const deps = makeDeps(undefined, undefined, undefined)
|
|
169
|
+
const cmd: ClientCommand = { type: "pty.kill", chatId: "c-10" }
|
|
170
|
+
const handled = await handleAgentCtrlCommand(deps, cmd, "r10")
|
|
171
|
+
expect(handled).toBe(true)
|
|
172
|
+
const ack = deps.sent[0] as { result: { ok: boolean; error: string } }
|
|
173
|
+
expect(ack.result.ok).toBe(false)
|
|
174
|
+
expect(ack.result.error).toContain("not available")
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test("pty.kill — delegates to killPtyInstance and acks with its result", async () => {
|
|
178
|
+
const killFn = mock(async (_chatId: string) => ({ ok: true }))
|
|
179
|
+
const deps = makeDeps(undefined, undefined, killFn)
|
|
180
|
+
const cmd: ClientCommand = { type: "pty.kill", chatId: "c-11" }
|
|
181
|
+
const handled = await handleAgentCtrlCommand(deps, cmd, "r11")
|
|
182
|
+
expect(handled).toBe(true)
|
|
183
|
+
expect(killFn).toHaveBeenCalledWith("c-11")
|
|
184
|
+
const ack = deps.sent[0] as { result: { ok: boolean } }
|
|
185
|
+
expect(ack.result.ok).toBe(true)
|
|
186
|
+
})
|
|
187
|
+
})
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ws-router-agent-ctrl.ts
|
|
3
|
+
*
|
|
4
|
+
* WS command handlers for agent-control lifecycle operations:
|
|
5
|
+
* autoContinue.accept, autoContinue.reschedule, autoContinue.cancel,
|
|
6
|
+
* tunnel.accept, tunnel.stop, tunnel.retry,
|
|
7
|
+
* pty.cancel, pty.kill
|
|
8
|
+
*
|
|
9
|
+
* Extracted from ws-router.ts.
|
|
10
|
+
*/
|
|
11
|
+
import { PROTOCOL_VERSION } 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 AgentCoordinator methods consumed by agent-ctrl WS commands. */
|
|
19
|
+
export interface AgentCtrlAgentDep {
|
|
20
|
+
acceptAutoContinue(chatId: string, scheduleId: string, scheduledAt: number): Promise<void>
|
|
21
|
+
rescheduleAutoContinue(chatId: string, scheduleId: string, scheduledAt: number): Promise<void>
|
|
22
|
+
cancelAutoContinue(chatId: string, scheduleId: string, reason: "user" | "chat_deleted"): Promise<void>
|
|
23
|
+
cancel(chatId: string): Promise<void>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The subset of TunnelGateway methods consumed by tunnel WS commands. */
|
|
27
|
+
export interface TunnelGatewayDep {
|
|
28
|
+
accept(chatId: string, tunnelId: string): Promise<void>
|
|
29
|
+
stop(chatId: string, tunnelId: string): Promise<void>
|
|
30
|
+
retry(chatId: string, tunnelId: string): Promise<void>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface AgentCtrlCommandDeps {
|
|
34
|
+
/** Agent coordinator methods for autoContinue and pty commands. */
|
|
35
|
+
agent: AgentCtrlAgentDep
|
|
36
|
+
/** Optional tunnel gateway (may be absent if tunnels are not configured). */
|
|
37
|
+
tunnelGateway: TunnelGatewayDep | undefined
|
|
38
|
+
/** Optional PTY-instance kill function (absent in SDK-only deploys). */
|
|
39
|
+
killPtyInstance: ((chatId: string) => Promise<{ ok: boolean; error?: string }>) | undefined
|
|
40
|
+
/** Pre-bound to the current WebSocket; called to send an ack envelope. */
|
|
41
|
+
send: (envelope: ServerEnvelope) => void
|
|
42
|
+
/** Called after operations that change the chat/sidebar snapshot. */
|
|
43
|
+
broadcastChatAndSidebar: (chatId: string) => Promise<void>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Command dispatcher
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Handle one agent-control WS command.
|
|
52
|
+
*
|
|
53
|
+
* Returns `true` when the command was handled (caller should `return`).
|
|
54
|
+
* Returns `false` when the command type is outside this module's scope.
|
|
55
|
+
*/
|
|
56
|
+
export async function handleAgentCtrlCommand(
|
|
57
|
+
deps: AgentCtrlCommandDeps,
|
|
58
|
+
command: ClientCommand,
|
|
59
|
+
id: string,
|
|
60
|
+
): Promise<boolean> {
|
|
61
|
+
const { agent, tunnelGateway, killPtyInstance, send, broadcastChatAndSidebar } = deps
|
|
62
|
+
|
|
63
|
+
switch (command.type) {
|
|
64
|
+
case "autoContinue.accept": {
|
|
65
|
+
await agent.acceptAutoContinue(command.chatId, command.scheduleId, command.scheduledAt)
|
|
66
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id })
|
|
67
|
+
await broadcastChatAndSidebar(command.chatId)
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
case "autoContinue.reschedule": {
|
|
71
|
+
await agent.rescheduleAutoContinue(command.chatId, command.scheduleId, command.scheduledAt)
|
|
72
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id })
|
|
73
|
+
await broadcastChatAndSidebar(command.chatId)
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
case "autoContinue.cancel": {
|
|
77
|
+
await agent.cancelAutoContinue(command.chatId, command.scheduleId, "user")
|
|
78
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id })
|
|
79
|
+
await broadcastChatAndSidebar(command.chatId)
|
|
80
|
+
return true
|
|
81
|
+
}
|
|
82
|
+
case "tunnel.accept": {
|
|
83
|
+
if (tunnelGateway) {
|
|
84
|
+
await tunnelGateway.accept(command.chatId, command.tunnelId)
|
|
85
|
+
}
|
|
86
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id })
|
|
87
|
+
await broadcastChatAndSidebar(command.chatId)
|
|
88
|
+
return true
|
|
89
|
+
}
|
|
90
|
+
case "tunnel.stop": {
|
|
91
|
+
if (tunnelGateway) {
|
|
92
|
+
await tunnelGateway.stop(command.chatId, command.tunnelId)
|
|
93
|
+
}
|
|
94
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id })
|
|
95
|
+
await broadcastChatAndSidebar(command.chatId)
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
case "tunnel.retry": {
|
|
99
|
+
if (tunnelGateway) {
|
|
100
|
+
await tunnelGateway.retry(command.chatId, command.tunnelId)
|
|
101
|
+
}
|
|
102
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id })
|
|
103
|
+
await broadcastChatAndSidebar(command.chatId)
|
|
104
|
+
return true
|
|
105
|
+
}
|
|
106
|
+
case "pty.cancel": {
|
|
107
|
+
try {
|
|
108
|
+
await agent.cancel(command.chatId)
|
|
109
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id, result: { ok: true } })
|
|
110
|
+
} catch (err) {
|
|
111
|
+
send({
|
|
112
|
+
v: PROTOCOL_VERSION,
|
|
113
|
+
type: "ack",
|
|
114
|
+
id,
|
|
115
|
+
result: { ok: false, error: err instanceof Error ? err.message : String(err) },
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
return true
|
|
119
|
+
}
|
|
120
|
+
case "pty.kill": {
|
|
121
|
+
if (!killPtyInstance) {
|
|
122
|
+
send({
|
|
123
|
+
v: PROTOCOL_VERSION,
|
|
124
|
+
type: "ack",
|
|
125
|
+
id,
|
|
126
|
+
result: { ok: false, error: "pty kill not available" },
|
|
127
|
+
})
|
|
128
|
+
return true
|
|
129
|
+
}
|
|
130
|
+
const result = await killPtyInstance(command.chatId)
|
|
131
|
+
send({ v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
132
|
+
return true
|
|
133
|
+
}
|
|
134
|
+
default:
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
}
|