@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
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the extracted auto-continue command handlers.
|
|
3
|
+
*
|
|
4
|
+
* Each test builds a minimal `AutoContinueCommandDeps` fake and asserts the
|
|
5
|
+
* correct behaviour of the function under test. No real IO or OS calls.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, test, expect } from "bun:test"
|
|
9
|
+
import { AUTO_CONTINUE_EVENT_VERSION } from "./auto-continue/events"
|
|
10
|
+
import type { AutoContinueEvent } from "./auto-continue/events"
|
|
11
|
+
import {
|
|
12
|
+
resolveAutoResumeFor,
|
|
13
|
+
emitAutoContinueEvent,
|
|
14
|
+
getChatSchedule,
|
|
15
|
+
requireFuture,
|
|
16
|
+
fireAutoContinue,
|
|
17
|
+
acceptAutoContinue,
|
|
18
|
+
rescheduleAutoContinue,
|
|
19
|
+
cancelAutoContinue,
|
|
20
|
+
type AutoContinueCommandDeps,
|
|
21
|
+
} from "./claude-autocontinue-commands"
|
|
22
|
+
import type { QueuedChatMessage, TranscriptEntry } from "../shared/types"
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Minimal fake builder
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
function makeQueuedMessage(): QueuedChatMessage {
|
|
29
|
+
return {
|
|
30
|
+
id: "q-1",
|
|
31
|
+
content: "continue",
|
|
32
|
+
attachments: [],
|
|
33
|
+
createdAt: Date.now(),
|
|
34
|
+
autoContinue: { scheduleId: "sched-1" },
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type StoredEvent = AutoContinueEvent
|
|
39
|
+
|
|
40
|
+
interface FakeStore {
|
|
41
|
+
events: StoredEvent[]
|
|
42
|
+
messages: { chatId: string; entry: TranscriptEntry }[]
|
|
43
|
+
chatExists: boolean
|
|
44
|
+
appendAutoContinueEvent(event: AutoContinueEvent): Promise<void>
|
|
45
|
+
getAutoContinueEvents(chatId: string): AutoContinueEvent[]
|
|
46
|
+
getChat(chatId: string): { id: string } | null | undefined
|
|
47
|
+
appendMessage(chatId: string, entry: TranscriptEntry): Promise<void>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function makeStore(overrides: Partial<FakeStore> = {}): FakeStore {
|
|
51
|
+
const store: FakeStore = {
|
|
52
|
+
events: [],
|
|
53
|
+
messages: [],
|
|
54
|
+
chatExists: true,
|
|
55
|
+
async appendAutoContinueEvent(event) {
|
|
56
|
+
store.events.push(event)
|
|
57
|
+
},
|
|
58
|
+
getAutoContinueEvents() {
|
|
59
|
+
return store.events
|
|
60
|
+
},
|
|
61
|
+
getChat(chatId) {
|
|
62
|
+
return store.chatExists ? { id: chatId } : null
|
|
63
|
+
},
|
|
64
|
+
async appendMessage(chatId, entry) {
|
|
65
|
+
store.messages.push({ chatId, entry })
|
|
66
|
+
},
|
|
67
|
+
...overrides,
|
|
68
|
+
}
|
|
69
|
+
return store
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function makeDeps(overrides: Partial<AutoContinueCommandDeps> = {}): AutoContinueCommandDeps {
|
|
73
|
+
const store = makeStore()
|
|
74
|
+
const autoResumeByChat = new Map<string, boolean>()
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
autoResumeByChat,
|
|
78
|
+
getAutoResumePreference: () => false,
|
|
79
|
+
store,
|
|
80
|
+
scheduleManager: null,
|
|
81
|
+
emitStateChange: () => {},
|
|
82
|
+
enqueueMessage: async () => makeQueuedMessage(),
|
|
83
|
+
maybeStartNextQueuedMessage: async () => false,
|
|
84
|
+
...overrides,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// resolveAutoResumeFor
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
describe("resolveAutoResumeFor", () => {
|
|
93
|
+
test("returns per-chat override when present", () => {
|
|
94
|
+
const autoResumeByChat = new Map<string, boolean>([["chat-1", true]])
|
|
95
|
+
const deps = makeDeps({ autoResumeByChat, getAutoResumePreference: () => false })
|
|
96
|
+
expect(resolveAutoResumeFor(deps, "chat-1")).toBe(true)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test("falls back to global preference when no override", () => {
|
|
100
|
+
const deps = makeDeps({ getAutoResumePreference: () => true })
|
|
101
|
+
expect(resolveAutoResumeFor(deps, "chat-missing")).toBe(true)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test("per-chat false wins over global true", () => {
|
|
105
|
+
const autoResumeByChat = new Map<string, boolean>([["chat-1", false]])
|
|
106
|
+
const deps = makeDeps({ autoResumeByChat, getAutoResumePreference: () => true })
|
|
107
|
+
expect(resolveAutoResumeFor(deps, "chat-1")).toBe(false)
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// emitAutoContinueEvent
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
describe("emitAutoContinueEvent", () => {
|
|
116
|
+
test("appends event to store", async () => {
|
|
117
|
+
const store = makeStore()
|
|
118
|
+
const deps = makeDeps({ store })
|
|
119
|
+
const event: AutoContinueEvent = {
|
|
120
|
+
v: AUTO_CONTINUE_EVENT_VERSION,
|
|
121
|
+
kind: "auto_continue_fired",
|
|
122
|
+
timestamp: Date.now(),
|
|
123
|
+
chatId: "chat-1",
|
|
124
|
+
scheduleId: "sched-1",
|
|
125
|
+
}
|
|
126
|
+
await emitAutoContinueEvent(deps, event)
|
|
127
|
+
expect(store.events).toHaveLength(1)
|
|
128
|
+
expect(store.events[0]).toEqual(event)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test("notifies schedule manager", async () => {
|
|
132
|
+
const notified: AutoContinueEvent[] = []
|
|
133
|
+
const deps = makeDeps({
|
|
134
|
+
scheduleManager: { onEvent: (e) => { notified.push(e) } },
|
|
135
|
+
})
|
|
136
|
+
const event: AutoContinueEvent = {
|
|
137
|
+
v: AUTO_CONTINUE_EVENT_VERSION,
|
|
138
|
+
kind: "auto_continue_fired",
|
|
139
|
+
timestamp: Date.now(),
|
|
140
|
+
chatId: "chat-1",
|
|
141
|
+
scheduleId: "sched-1",
|
|
142
|
+
}
|
|
143
|
+
await emitAutoContinueEvent(deps, event)
|
|
144
|
+
expect(notified).toHaveLength(1)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test("emits state change for chat", async () => {
|
|
148
|
+
const changes: string[] = []
|
|
149
|
+
const deps = makeDeps({ emitStateChange: (id) => { changes.push(id) } })
|
|
150
|
+
const event: AutoContinueEvent = {
|
|
151
|
+
v: AUTO_CONTINUE_EVENT_VERSION,
|
|
152
|
+
kind: "auto_continue_fired",
|
|
153
|
+
timestamp: Date.now(),
|
|
154
|
+
chatId: "chat-42",
|
|
155
|
+
scheduleId: "sched-1",
|
|
156
|
+
}
|
|
157
|
+
await emitAutoContinueEvent(deps, event)
|
|
158
|
+
expect(changes).toContain("chat-42")
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// getChatSchedule
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
describe("getChatSchedule", () => {
|
|
167
|
+
test("returns undefined when no events", () => {
|
|
168
|
+
const deps = makeDeps()
|
|
169
|
+
expect(getChatSchedule(deps, "chat-1", "sched-1")).toBeUndefined()
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
test("returns the schedule when a proposed event exists", async () => {
|
|
173
|
+
const deps = makeDeps()
|
|
174
|
+
const event: AutoContinueEvent = {
|
|
175
|
+
v: AUTO_CONTINUE_EVENT_VERSION,
|
|
176
|
+
kind: "auto_continue_proposed",
|
|
177
|
+
timestamp: Date.now(),
|
|
178
|
+
chatId: "chat-1",
|
|
179
|
+
scheduleId: "sched-1",
|
|
180
|
+
detectedAt: Date.now() - 100,
|
|
181
|
+
resetAt: Date.now() + 3_600_000,
|
|
182
|
+
tz: "UTC",
|
|
183
|
+
}
|
|
184
|
+
await deps.store.appendAutoContinueEvent(event)
|
|
185
|
+
const result = getChatSchedule(deps, "chat-1", "sched-1")
|
|
186
|
+
expect(result?.state).toBe("proposed")
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// requireFuture
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
describe("requireFuture", () => {
|
|
195
|
+
test("does not throw when scheduledAt is in the future", () => {
|
|
196
|
+
expect(() => requireFuture(Date.now() + 60_000)).not.toThrow()
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
test("throws when scheduledAt is now", () => {
|
|
200
|
+
expect(() => requireFuture(Date.now() - 1)).toThrow("scheduledAt must be in the future")
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
test("throws when scheduledAt is in the past", () => {
|
|
204
|
+
expect(() => requireFuture(Date.now() - 5_000)).toThrow()
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// fireAutoContinue
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
describe("fireAutoContinue", () => {
|
|
213
|
+
test("no-ops when chat does not exist", async () => {
|
|
214
|
+
const store = makeStore({ chatExists: false })
|
|
215
|
+
const enqueued: string[] = []
|
|
216
|
+
const deps = makeDeps({
|
|
217
|
+
store,
|
|
218
|
+
enqueueMessage: async (_, content) => { enqueued.push(content); return makeQueuedMessage() },
|
|
219
|
+
})
|
|
220
|
+
await fireAutoContinue(deps, "chat-1", "sched-1")
|
|
221
|
+
expect(enqueued).toHaveLength(0)
|
|
222
|
+
expect(store.events).toHaveLength(0)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test("appends fired event and enqueues prompt", async () => {
|
|
226
|
+
const store = makeStore()
|
|
227
|
+
const enqueued: string[] = []
|
|
228
|
+
const deps = makeDeps({
|
|
229
|
+
store,
|
|
230
|
+
enqueueMessage: async (_, content) => { enqueued.push(content); return makeQueuedMessage() },
|
|
231
|
+
})
|
|
232
|
+
await fireAutoContinue(deps, "chat-1", "sched-1")
|
|
233
|
+
expect(store.events).toHaveLength(1)
|
|
234
|
+
expect(store.events[0]?.kind).toBe("auto_continue_fired")
|
|
235
|
+
expect(enqueued).toContain("continue") // fallback when no schedule prompt
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
test("uses schedule prompt when present", async () => {
|
|
239
|
+
const store = makeStore()
|
|
240
|
+
// Seed a proposed event so getChatSchedule finds it
|
|
241
|
+
await store.appendAutoContinueEvent({
|
|
242
|
+
v: AUTO_CONTINUE_EVENT_VERSION,
|
|
243
|
+
kind: "auto_continue_proposed",
|
|
244
|
+
timestamp: Date.now(),
|
|
245
|
+
chatId: "chat-1",
|
|
246
|
+
scheduleId: "sched-1",
|
|
247
|
+
detectedAt: Date.now() - 100,
|
|
248
|
+
resetAt: Date.now() + 3_600_000,
|
|
249
|
+
tz: "UTC",
|
|
250
|
+
})
|
|
251
|
+
// Accept it so the schedule has the prompt
|
|
252
|
+
await store.appendAutoContinueEvent({
|
|
253
|
+
v: AUTO_CONTINUE_EVENT_VERSION,
|
|
254
|
+
kind: "auto_continue_accepted",
|
|
255
|
+
timestamp: Date.now(),
|
|
256
|
+
chatId: "chat-1",
|
|
257
|
+
scheduleId: "sched-1",
|
|
258
|
+
scheduledAt: Date.now() + 5_000,
|
|
259
|
+
tz: "UTC",
|
|
260
|
+
source: "subagent_background",
|
|
261
|
+
resetAt: Date.now() + 3_600_000,
|
|
262
|
+
detectedAt: Date.now() - 100,
|
|
263
|
+
prompt: "Read PROGRESS.md and decide next step",
|
|
264
|
+
})
|
|
265
|
+
const enqueued: string[] = []
|
|
266
|
+
const deps = makeDeps({
|
|
267
|
+
store,
|
|
268
|
+
enqueueMessage: async (_, content) => { enqueued.push(content); return makeQueuedMessage() },
|
|
269
|
+
})
|
|
270
|
+
await fireAutoContinue(deps, "chat-1", "sched-1")
|
|
271
|
+
expect(enqueued).toContain("Read PROGRESS.md and decide next step")
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
test("appends error message when enqueueMessage throws", async () => {
|
|
275
|
+
const store = makeStore()
|
|
276
|
+
const deps = makeDeps({
|
|
277
|
+
store,
|
|
278
|
+
enqueueMessage: async () => { throw new Error("queue full") },
|
|
279
|
+
})
|
|
280
|
+
await fireAutoContinue(deps, "chat-1", "sched-1")
|
|
281
|
+
expect(store.messages).toHaveLength(1)
|
|
282
|
+
const msg = store.messages[0]?.entry
|
|
283
|
+
expect(msg && "result" in msg ? msg.result : "").toContain("Auto-continue failed: queue full")
|
|
284
|
+
})
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
// acceptAutoContinue
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
describe("acceptAutoContinue", () => {
|
|
292
|
+
async function seedProposed(store: FakeStore) {
|
|
293
|
+
await store.appendAutoContinueEvent({
|
|
294
|
+
v: AUTO_CONTINUE_EVENT_VERSION,
|
|
295
|
+
kind: "auto_continue_proposed",
|
|
296
|
+
timestamp: Date.now(),
|
|
297
|
+
chatId: "chat-1",
|
|
298
|
+
scheduleId: "sched-1",
|
|
299
|
+
detectedAt: Date.now() - 100,
|
|
300
|
+
resetAt: Date.now() + 3_600_000,
|
|
301
|
+
tz: "UTC",
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
test("throws when schedule not found", async () => {
|
|
306
|
+
const deps = makeDeps()
|
|
307
|
+
await expect(
|
|
308
|
+
acceptAutoContinue(deps, "chat-1", "sched-missing", Date.now() + 60_000),
|
|
309
|
+
).rejects.toThrow("Schedule not found")
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
test("throws when schedule not in proposed state", async () => {
|
|
313
|
+
const store = makeStore()
|
|
314
|
+
await seedProposed(store)
|
|
315
|
+
// Cancel it first
|
|
316
|
+
await store.appendAutoContinueEvent({
|
|
317
|
+
v: AUTO_CONTINUE_EVENT_VERSION,
|
|
318
|
+
kind: "auto_continue_cancelled",
|
|
319
|
+
timestamp: Date.now(),
|
|
320
|
+
chatId: "chat-1",
|
|
321
|
+
scheduleId: "sched-1",
|
|
322
|
+
reason: "user",
|
|
323
|
+
})
|
|
324
|
+
const deps = makeDeps({ store })
|
|
325
|
+
await expect(
|
|
326
|
+
acceptAutoContinue(deps, "chat-1", "sched-1", Date.now() + 60_000),
|
|
327
|
+
).rejects.toThrow("Schedule not pending")
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
test("throws when scheduledAt is in the past", async () => {
|
|
331
|
+
const store = makeStore()
|
|
332
|
+
await seedProposed(store)
|
|
333
|
+
const deps = makeDeps({ store })
|
|
334
|
+
await expect(
|
|
335
|
+
acceptAutoContinue(deps, "chat-1", "sched-1", Date.now() - 1),
|
|
336
|
+
).rejects.toThrow("scheduledAt must be in the future")
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
test("appends accepted event when valid", async () => {
|
|
340
|
+
const store = makeStore()
|
|
341
|
+
await seedProposed(store)
|
|
342
|
+
const deps = makeDeps({ store })
|
|
343
|
+
await acceptAutoContinue(deps, "chat-1", "sched-1", Date.now() + 60_000)
|
|
344
|
+
const accepted = store.events.find((e) => e.kind === "auto_continue_accepted")
|
|
345
|
+
expect(accepted).toBeDefined()
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
// rescheduleAutoContinue
|
|
351
|
+
// ---------------------------------------------------------------------------
|
|
352
|
+
|
|
353
|
+
describe("rescheduleAutoContinue", () => {
|
|
354
|
+
async function seedScheduled(store: FakeStore) {
|
|
355
|
+
await store.appendAutoContinueEvent({
|
|
356
|
+
v: AUTO_CONTINUE_EVENT_VERSION,
|
|
357
|
+
kind: "auto_continue_proposed",
|
|
358
|
+
timestamp: Date.now(),
|
|
359
|
+
chatId: "chat-1",
|
|
360
|
+
scheduleId: "sched-1",
|
|
361
|
+
detectedAt: Date.now() - 200,
|
|
362
|
+
resetAt: Date.now() + 3_600_000,
|
|
363
|
+
tz: "UTC",
|
|
364
|
+
})
|
|
365
|
+
await store.appendAutoContinueEvent({
|
|
366
|
+
v: AUTO_CONTINUE_EVENT_VERSION,
|
|
367
|
+
kind: "auto_continue_accepted",
|
|
368
|
+
timestamp: Date.now(),
|
|
369
|
+
chatId: "chat-1",
|
|
370
|
+
scheduleId: "sched-1",
|
|
371
|
+
scheduledAt: Date.now() + 30_000,
|
|
372
|
+
tz: "UTC",
|
|
373
|
+
source: "user",
|
|
374
|
+
resetAt: Date.now() + 3_600_000,
|
|
375
|
+
detectedAt: Date.now() - 200,
|
|
376
|
+
})
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
test("throws when schedule not found", async () => {
|
|
380
|
+
const deps = makeDeps()
|
|
381
|
+
await expect(
|
|
382
|
+
rescheduleAutoContinue(deps, "chat-1", "sched-missing", Date.now() + 60_000),
|
|
383
|
+
).rejects.toThrow("Schedule not active")
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
test("throws when scheduledAt is in the past", async () => {
|
|
387
|
+
const store = makeStore()
|
|
388
|
+
await seedScheduled(store)
|
|
389
|
+
const deps = makeDeps({ store })
|
|
390
|
+
await expect(
|
|
391
|
+
rescheduleAutoContinue(deps, "chat-1", "sched-1", Date.now() - 1),
|
|
392
|
+
).rejects.toThrow("scheduledAt must be in the future")
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
test("appends rescheduled event when valid", async () => {
|
|
396
|
+
const store = makeStore()
|
|
397
|
+
await seedScheduled(store)
|
|
398
|
+
const deps = makeDeps({ store })
|
|
399
|
+
await rescheduleAutoContinue(deps, "chat-1", "sched-1", Date.now() + 60_000)
|
|
400
|
+
const rescheduled = store.events.find((e) => e.kind === "auto_continue_rescheduled")
|
|
401
|
+
expect(rescheduled).toBeDefined()
|
|
402
|
+
})
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
// ---------------------------------------------------------------------------
|
|
406
|
+
// cancelAutoContinue
|
|
407
|
+
// ---------------------------------------------------------------------------
|
|
408
|
+
|
|
409
|
+
describe("cancelAutoContinue", () => {
|
|
410
|
+
test("no-ops silently when schedule not found", async () => {
|
|
411
|
+
const store = makeStore()
|
|
412
|
+
const deps = makeDeps({ store })
|
|
413
|
+
await expect(
|
|
414
|
+
cancelAutoContinue(deps, "chat-1", "sched-missing", "user"),
|
|
415
|
+
).resolves.toBeUndefined()
|
|
416
|
+
expect(store.events).toHaveLength(0)
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
test("no-ops when schedule already fired", async () => {
|
|
420
|
+
const store = makeStore()
|
|
421
|
+
await store.appendAutoContinueEvent({
|
|
422
|
+
v: AUTO_CONTINUE_EVENT_VERSION,
|
|
423
|
+
kind: "auto_continue_proposed",
|
|
424
|
+
timestamp: Date.now(),
|
|
425
|
+
chatId: "chat-1",
|
|
426
|
+
scheduleId: "sched-1",
|
|
427
|
+
detectedAt: Date.now() - 100,
|
|
428
|
+
resetAt: Date.now() + 3_600_000,
|
|
429
|
+
tz: "UTC",
|
|
430
|
+
})
|
|
431
|
+
await store.appendAutoContinueEvent({
|
|
432
|
+
v: AUTO_CONTINUE_EVENT_VERSION,
|
|
433
|
+
kind: "auto_continue_fired",
|
|
434
|
+
timestamp: Date.now(),
|
|
435
|
+
chatId: "chat-1",
|
|
436
|
+
scheduleId: "sched-1",
|
|
437
|
+
})
|
|
438
|
+
const eventsBefore = store.events.length
|
|
439
|
+
const deps = makeDeps({ store })
|
|
440
|
+
await cancelAutoContinue(deps, "chat-1", "sched-1", "user")
|
|
441
|
+
expect(store.events).toHaveLength(eventsBefore)
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
test("appends cancelled event when proposed", async () => {
|
|
445
|
+
const store = makeStore()
|
|
446
|
+
await store.appendAutoContinueEvent({
|
|
447
|
+
v: AUTO_CONTINUE_EVENT_VERSION,
|
|
448
|
+
kind: "auto_continue_proposed",
|
|
449
|
+
timestamp: Date.now(),
|
|
450
|
+
chatId: "chat-1",
|
|
451
|
+
scheduleId: "sched-1",
|
|
452
|
+
detectedAt: Date.now() - 100,
|
|
453
|
+
resetAt: Date.now() + 3_600_000,
|
|
454
|
+
tz: "UTC",
|
|
455
|
+
})
|
|
456
|
+
const deps = makeDeps({ store })
|
|
457
|
+
await cancelAutoContinue(deps, "chat-1", "sched-1", "user")
|
|
458
|
+
const cancelled = store.events.find((e) => e.kind === "auto_continue_cancelled")
|
|
459
|
+
expect(cancelled).toBeDefined()
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
test("appends cancelled event when scheduled", async () => {
|
|
463
|
+
const store = makeStore()
|
|
464
|
+
await store.appendAutoContinueEvent({
|
|
465
|
+
v: AUTO_CONTINUE_EVENT_VERSION,
|
|
466
|
+
kind: "auto_continue_proposed",
|
|
467
|
+
timestamp: Date.now(),
|
|
468
|
+
chatId: "chat-1",
|
|
469
|
+
scheduleId: "sched-1",
|
|
470
|
+
detectedAt: Date.now() - 200,
|
|
471
|
+
resetAt: Date.now() + 3_600_000,
|
|
472
|
+
tz: "UTC",
|
|
473
|
+
})
|
|
474
|
+
await store.appendAutoContinueEvent({
|
|
475
|
+
v: AUTO_CONTINUE_EVENT_VERSION,
|
|
476
|
+
kind: "auto_continue_accepted",
|
|
477
|
+
timestamp: Date.now(),
|
|
478
|
+
chatId: "chat-1",
|
|
479
|
+
scheduleId: "sched-1",
|
|
480
|
+
scheduledAt: Date.now() + 30_000,
|
|
481
|
+
tz: "UTC",
|
|
482
|
+
source: "user",
|
|
483
|
+
resetAt: Date.now() + 3_600_000,
|
|
484
|
+
detectedAt: Date.now() - 200,
|
|
485
|
+
})
|
|
486
|
+
const deps = makeDeps({ store })
|
|
487
|
+
await cancelAutoContinue(deps, "chat-1", "sched-1", "chat_deleted")
|
|
488
|
+
const cancelled = store.events.find((e) => e.kind === "auto_continue_cancelled")
|
|
489
|
+
expect(cancelled).toBeDefined()
|
|
490
|
+
if (cancelled && cancelled.kind === "auto_continue_cancelled") {
|
|
491
|
+
expect(cancelled.reason).toBe("chat_deleted")
|
|
492
|
+
}
|
|
493
|
+
})
|
|
494
|
+
})
|