kimaki 1.3.0 → 1.5.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/assets/fonts/Geist-Bold.ttf +0 -0
- package/assets/fonts/Geist-Regular.ttf +0 -0
- package/assets/fonts/GeistMono-Regular.ttf +0 -0
- package/assets/fonts/OFL.txt +92 -0
- package/dist/agent-ui.e2e.test.js +27 -13
- package/dist/agent-ui.e2e.test.js.map +1 -1
- package/dist/agent-ui.js +2 -1
- package/dist/agent-ui.js.map +1 -1
- package/dist/analytics.e2e.test.js +1 -1
- package/dist/analytics.e2e.test.js.map +1 -1
- package/dist/autostart.d.ts +4 -0
- package/dist/autostart.js +55 -10
- package/dist/autostart.js.map +1 -1
- package/dist/bot.d.ts +13 -6
- package/dist/bot.js +17 -15
- package/dist/bot.js.map +1 -1
- package/dist/btw.e2e.test.js +3 -3
- package/dist/builtin-skills.d.ts +3 -0
- package/dist/builtin-skills.js +29 -0
- package/dist/builtin-skills.js.map +1 -1
- package/dist/builtin-skills.test.js +72 -1
- package/dist/builtin-skills.test.js.map +1 -1
- package/dist/catalog-refresh.e2e.test.js +2 -2
- package/dist/cli/bot.d.ts +1 -1
- package/dist/cli/bot.js +33 -18
- package/dist/cli/bot.js.map +1 -1
- package/dist/cli/discord.js +32 -6
- package/dist/cli/discord.js.map +1 -1
- package/dist/cli/schedule.js +23 -9
- package/dist/cli/schedule.js.map +1 -1
- package/dist/cli/session.js +37 -12
- package/dist/cli/session.js.map +1 -1
- package/dist/cli/shared.d.ts +5 -3
- package/dist/cli/shared.js +45 -10
- package/dist/cli/shared.js.map +1 -1
- package/dist/cli-discord.e2e.test.js +13 -8
- package/dist/cli-discord.e2e.test.js.map +1 -1
- package/dist/cli-lock.e2e.test.js +87 -20
- package/dist/cli-lock.e2e.test.js.map +1 -1
- package/dist/cli-read.e2e.test.js +18 -9
- package/dist/cli-read.e2e.test.js.map +1 -1
- package/dist/cli-sessions.e2e.test.js +4 -3
- package/dist/cli-sessions.e2e.test.js.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/preference-commands.d.ts +4 -0
- package/dist/commands/preference-commands.js +38 -4
- package/dist/commands/preference-commands.js.map +1 -1
- package/dist/commands/session-commands.js +11 -8
- package/dist/commands/session-commands.js.map +1 -1
- package/dist/commands/thread-commands.js +94 -24
- package/dist/commands/thread-commands.js.map +1 -1
- package/dist/commands/worktree-commands.d.ts +5 -6
- package/dist/commands/worktree-commands.js +8 -7
- package/dist/commands/worktree-commands.js.map +1 -1
- package/dist/commands.e2e.test.js +25 -21
- package/dist/commands.e2e.test.js.map +1 -1
- package/dist/coordinator-agents.e2e.test.d.ts +1 -0
- package/dist/coordinator-agents.e2e.test.js +113 -0
- package/dist/coordinator-agents.e2e.test.js.map +1 -0
- package/dist/coordinator-idle.e2e.test.d.ts +1 -0
- package/dist/coordinator-idle.e2e.test.js +100 -0
- package/dist/coordinator-idle.e2e.test.js.map +1 -0
- package/dist/coordinator-queue.e2e.test.d.ts +1 -0
- package/dist/coordinator-queue.e2e.test.js +244 -0
- package/dist/coordinator-queue.e2e.test.js.map +1 -0
- package/dist/coordinator-warmup.e2e.test.d.ts +1 -0
- package/dist/coordinator-warmup.e2e.test.js +131 -0
- package/dist/coordinator-warmup.e2e.test.js.map +1 -0
- package/dist/coordinator.d.ts +6 -0
- package/dist/coordinator.e2e.test.d.ts +1 -0
- package/dist/coordinator.e2e.test.js +505 -0
- package/dist/coordinator.e2e.test.js.map +1 -0
- package/dist/coordinator.js +164 -0
- package/dist/coordinator.js.map +1 -0
- package/dist/db.d.ts +78 -3
- package/dist/db.test.js +1 -0
- package/dist/db.test.js.map +1 -1
- package/dist/diagram.d.ts +11 -0
- package/dist/diagram.js +232 -0
- package/dist/diagram.js.map +1 -0
- package/dist/diagram.test.d.ts +1 -0
- package/dist/diagram.test.js +52 -0
- package/dist/diagram.test.js.map +1 -0
- package/dist/duration.js +13 -5
- package/dist/duration.js.map +1 -1
- package/dist/effects.d.ts +6 -1
- package/dist/effects.js +97 -26
- package/dist/effects.js.map +1 -1
- package/dist/event-loop.d.ts +2 -3
- package/dist/event-loop.js +74 -44
- package/dist/event-loop.js.map +1 -1
- package/dist/format-parts.d.ts +21 -4
- package/dist/format-parts.js +40 -23
- package/dist/format-parts.js.map +1 -1
- package/dist/gateway-replay.e2e.test.js +4 -4
- package/dist/harness.e2e.test.js +2 -2
- package/dist/harness.e2e.test.js.map +1 -1
- package/dist/hello-thread.e2e.test.js +56 -5
- package/dist/hello-thread.e2e.test.js.map +1 -1
- package/dist/ingress.js +47 -12
- package/dist/ingress.js.map +1 -1
- package/dist/interaction-context.d.ts +4 -4
- package/dist/interaction-context.js +9 -7
- package/dist/interaction-context.js.map +1 -1
- package/dist/interrupt.e2e.test.js +8 -5
- package/dist/interrupt.e2e.test.js.map +1 -1
- package/dist/legacy.e2e.test.js +1 -1
- package/dist/lock-routes.d.ts +83 -31
- package/dist/lock-routes.js +68 -8
- package/dist/lock-routes.js.map +1 -1
- package/dist/lock-server.d.ts +3 -2
- package/dist/lock-server.js +23 -7
- package/dist/lock-server.js.map +1 -1
- package/dist/main.d.ts +6 -3
- package/dist/main.js +14 -4
- package/dist/main.js.map +1 -1
- package/dist/mainmast-idle.e2e.test.d.ts +1 -0
- package/dist/mainmast-idle.e2e.test.js +99 -0
- package/dist/mainmast-idle.e2e.test.js.map +1 -0
- package/dist/mainmast-queue.e2e.test.d.ts +1 -0
- package/dist/mainmast-queue.e2e.test.js +244 -0
- package/dist/mainmast-queue.e2e.test.js.map +1 -0
- package/dist/mainmast-warmup.e2e.test.d.ts +1 -0
- package/dist/mainmast-warmup.e2e.test.js +130 -0
- package/dist/mainmast-warmup.e2e.test.js.map +1 -0
- package/dist/mainmast.d.ts +6 -0
- package/dist/mainmast.e2e.test.d.ts +1 -0
- package/dist/mainmast.e2e.test.js +428 -0
- package/dist/mainmast.e2e.test.js.map +1 -0
- package/dist/mainmast.js +156 -0
- package/dist/mainmast.js.map +1 -0
- package/dist/markdown/markdown.test.js +14 -4
- package/dist/markdown/markdown.test.js.map +1 -1
- package/dist/markdown/render-markdown.js +9 -6
- package/dist/markdown/render-markdown.js.map +1 -1
- package/dist/onboarding-cli.e2e.test.js +7 -6
- package/dist/onboarding-cli.e2e.test.js.map +1 -1
- package/dist/onboarding.d.ts +3 -10
- package/dist/onboarding.e2e.test.js +8 -10
- package/dist/onboarding.e2e.test.js.map +1 -1
- package/dist/onboarding.js +14 -24
- package/dist/onboarding.js.map +1 -1
- package/dist/permissions.e2e.test.js +3 -3
- package/dist/plugin/coordinator.d.ts +4 -0
- package/dist/plugin/coordinator.js +389 -0
- package/dist/plugin/coordinator.js.map +1 -0
- package/dist/plugin/index.js +93 -61
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/mainmast.d.ts +4 -0
- package/dist/plugin/mainmast.js +379 -0
- package/dist/plugin/mainmast.js.map +1 -0
- package/dist/plugin.e2e.test.js +1 -1
- package/dist/preference-commands.e2e.test.js +103 -24
- package/dist/preference-commands.e2e.test.js.map +1 -1
- package/dist/project.d.ts +1 -1
- package/dist/prompt.d.ts +22 -9
- package/dist/prompt.js +64 -48
- package/dist/prompt.js.map +1 -1
- package/dist/questions.e2e.test.js +13 -13
- package/dist/queue-sources.e2e.test.d.ts +1 -0
- package/dist/queue-sources.e2e.test.js +168 -0
- package/dist/queue-sources.e2e.test.js.map +1 -0
- package/dist/queue.d.ts +9 -1
- package/dist/queue.e2e.test.js +48 -30
- package/dist/queue.e2e.test.js.map +1 -1
- package/dist/queue.js +97 -24
- package/dist/queue.js.map +1 -1
- package/dist/queue.test.d.ts +1 -0
- package/dist/queue.test.js +57 -0
- package/dist/queue.test.js.map +1 -0
- package/dist/remote-send.e2e.test.js +6 -6
- package/dist/remote-send.js +2 -1
- package/dist/remote-send.js.map +1 -1
- package/dist/renderer.e2e.test.js +7 -7
- package/dist/restart.e2e.test.js +2 -2
- package/dist/routines.e2e.test.js +2 -2
- package/dist/scheduler.d.ts +3 -3
- package/dist/scheduler.e2e.test.js +43 -20
- package/dist/scheduler.e2e.test.js.map +1 -1
- package/dist/scheduler.js +4 -2
- package/dist/scheduler.js.map +1 -1
- package/dist/schema-sql.d.ts +1 -1
- package/dist/schema-sql.js +1 -1
- package/dist/schema-sql.js.map +1 -1
- package/dist/schema.d.ts +152 -6
- package/dist/schema.js +12 -0
- package/dist/schema.js.map +1 -1
- package/dist/session-commands.e2e.test.js +36 -11
- package/dist/session-commands.e2e.test.js.map +1 -1
- package/dist/session-events.d.ts +18 -6
- package/dist/session-events.js +175 -27
- package/dist/session-events.js.map +1 -1
- package/dist/session-ops.e2e.test.js +63 -12
- package/dist/session-ops.e2e.test.js.map +1 -1
- package/dist/sessions.d.ts +44 -21
- package/dist/sessions.js +177 -65
- package/dist/sessions.js.map +1 -1
- package/dist/shell.e2e.test.js +4 -4
- package/dist/slash-commands.js +6 -4
- package/dist/slash-commands.js.map +1 -1
- package/dist/slash-commands.test.js +2 -0
- package/dist/slash-commands.test.js.map +1 -1
- package/dist/sleep.e2e.test.js +29 -14
- package/dist/sleep.e2e.test.js.map +1 -1
- package/dist/sleeps.d.ts +2 -1
- package/dist/sleeps.js +21 -3
- package/dist/sleeps.js.map +1 -1
- package/dist/store.d.ts +1 -0
- package/dist/store.js +3 -2
- package/dist/store.js.map +1 -1
- package/dist/system-prompt.d.ts +5 -15
- package/dist/system-prompt.js +180 -131
- package/dist/system-prompt.js.map +1 -1
- package/dist/test/generate-log-plugin/index.d.ts +3 -0
- package/dist/test/generate-log-plugin/index.js +15 -0
- package/dist/test/generate-log-plugin/index.js.map +1 -0
- package/dist/test/harness.d.ts +20 -2
- package/dist/test/harness.js +51 -11
- package/dist/test/harness.js.map +1 -1
- package/dist/test/replay.js +11 -8
- package/dist/test/replay.js.map +1 -1
- package/dist/thread-reducer.d.ts +29 -6
- package/dist/thread-reducer.fixtures.test.js +58 -51
- package/dist/thread-reducer.fixtures.test.js.map +1 -1
- package/dist/thread-reducer.js +109 -50
- package/dist/thread-reducer.js.map +1 -1
- package/dist/thread-reducer.test.js +190 -48
- package/dist/thread-reducer.test.js.map +1 -1
- package/dist/tunnel.d.ts +9 -0
- package/dist/tunnel.js +117 -28
- package/dist/tunnel.js.map +1 -1
- package/dist/tunnel.test.js +81 -5
- package/dist/tunnel.test.js.map +1 -1
- package/dist/voice-attachments.e2e.test.js +108 -18
- package/dist/voice-attachments.e2e.test.js.map +1 -1
- package/dist/voice-call.e2e.test.js +9 -9
- package/dist/voice-call.js +6 -6
- package/dist/voice-call.js.map +1 -1
- package/dist/voice-openai.e2e.test.js +59 -23
- package/dist/voice-openai.e2e.test.js.map +1 -1
- package/dist/voice.d.ts +1 -1
- package/dist/voice.js +72 -24
- package/dist/voice.js.map +1 -1
- package/dist/working-directory.e2e.test.js +11 -11
- package/dist/working-directory.e2e.test.js.map +1 -1
- package/package.json +11 -5
- package/skills/kimaki-tunnel/SKILL.md +3 -2
- package/src/agent-ui.e2e.test.ts +27 -13
- package/src/agent-ui.ts +2 -1
- package/src/analytics.e2e.test.ts +1 -1
- package/src/autostart.ts +55 -10
- package/src/bot.ts +36 -19
- package/src/btw.e2e.test.ts +3 -3
- package/src/builtin-skills.test.ts +73 -1
- package/src/builtin-skills.ts +33 -0
- package/src/catalog-refresh.e2e.test.ts +2 -2
- package/src/cli/bot.ts +30 -19
- package/src/cli/discord.ts +30 -6
- package/src/cli/schedule.ts +23 -9
- package/src/cli/session.ts +35 -12
- package/src/cli/shared.ts +43 -8
- package/src/cli-discord.e2e.test.ts +13 -8
- package/src/cli-lock.e2e.test.ts +88 -20
- package/src/cli-read.e2e.test.ts +17 -8
- package/src/cli-sessions.e2e.test.ts +4 -3
- package/src/cli.ts +2 -0
- package/src/commands/preference-commands.ts +38 -4
- package/src/commands/session-commands.ts +12 -7
- package/src/commands/thread-commands.ts +92 -24
- package/src/commands/worktree-commands.ts +10 -9
- package/src/commands.e2e.test.ts +25 -21
- package/src/coordinator-agents.e2e.test.ts +145 -0
- package/src/coordinator-idle.e2e.test.ts +122 -0
- package/src/coordinator-queue.e2e.test.ts +279 -0
- package/src/coordinator-warmup.e2e.test.ts +158 -0
- package/src/coordinator.e2e.test.ts +545 -0
- package/src/coordinator.ts +168 -0
- package/src/db.test.ts +1 -0
- package/src/diagram.test.ts +56 -0
- package/src/diagram.ts +243 -0
- package/src/duration.ts +13 -5
- package/src/effects.ts +99 -27
- package/src/event-loop.ts +71 -45
- package/src/format-parts.ts +53 -22
- package/src/gateway-replay.e2e.test.ts +4 -4
- package/src/harness.e2e.test.ts +2 -2
- package/src/hello-thread.e2e.test.ts +59 -5
- package/src/ingress.ts +44 -11
- package/src/interaction-context.ts +12 -11
- package/src/interrupt.e2e.test.ts +8 -5
- package/src/legacy.e2e.test.ts +1 -1
- package/src/lock-routes.ts +62 -8
- package/src/lock-server.ts +23 -7
- package/src/main.ts +26 -10
- package/src/markdown/markdown.test.ts +15 -4
- package/src/markdown/render-markdown.ts +9 -6
- package/src/onboarding-cli.e2e.test.ts +7 -6
- package/src/onboarding.e2e.test.ts +8 -10
- package/src/onboarding.ts +13 -29
- package/src/permissions.e2e.test.ts +3 -3
- package/src/plugin/coordinator.ts +401 -0
- package/src/plugin/index.ts +86 -59
- package/src/plugin.e2e.test.ts +1 -1
- package/src/preference-commands.e2e.test.ts +103 -23
- package/src/prompt.ts +87 -43
- package/src/questions.e2e.test.ts +13 -13
- package/src/queue-sources.e2e.test.ts +197 -0
- package/src/queue.e2e.test.ts +48 -30
- package/src/queue.test.ts +59 -0
- package/src/queue.ts +104 -24
- package/src/remote-send.e2e.test.ts +6 -6
- package/src/remote-send.ts +2 -1
- package/src/renderer.e2e.test.ts +7 -7
- package/src/restart.e2e.test.ts +2 -2
- package/src/routines.e2e.test.ts +2 -2
- package/src/scheduler.e2e.test.ts +45 -19
- package/src/scheduler.ts +4 -2
- package/src/schema-sql.ts +1 -1
- package/src/schema.ts +13 -0
- package/src/session-commands.e2e.test.ts +38 -11
- package/src/session-events.ts +168 -25
- package/src/session-ops.e2e.test.ts +64 -11
- package/src/sessions.ts +217 -67
- package/src/shell.e2e.test.ts +4 -4
- package/src/slash-commands.test.ts +2 -0
- package/src/slash-commands.ts +6 -4
- package/src/sleep.e2e.test.ts +30 -13
- package/src/sleeps.ts +20 -3
- package/src/store.ts +5 -2
- package/src/system-prompt.ts +190 -134
- package/src/test/generate-log-plugin/index.ts +16 -0
- package/src/test/harness.ts +77 -17
- package/src/test/replay.ts +12 -7
- package/src/thread-reducer.fixtures.test.ts +58 -51
- package/src/thread-reducer.test.ts +199 -49
- package/src/thread-reducer.ts +135 -66
- package/src/tunnel.test.ts +81 -5
- package/src/tunnel.ts +124 -25
- package/src/voice-attachments.e2e.test.ts +110 -18
- package/src/voice-call.e2e.test.ts +9 -9
- package/src/voice-call.ts +6 -6
- package/src/voice-openai.e2e.test.ts +64 -24
- package/src/voice.ts +93 -26
- package/src/working-directory.e2e.test.ts +11 -11
- package/src/message-tool.e2e.test.ts +0 -45
|
@@ -13,7 +13,8 @@ test('text-only turn: banner, text, typing, footer with context percent', () =>
|
|
|
13
13
|
expect(effects).toMatchInlineSnapshot(`
|
|
14
14
|
[
|
|
15
15
|
{
|
|
16
|
-
"
|
|
16
|
+
"echo": true,
|
|
17
|
+
"text": "-# » **user:** Do these steps with tools, one by one: 1) run \`ls cli/src | head -5\` with bash, 2) write a file tmp-events/hello.txt containing "hello", 3) edit that file to replace hello with "hello world", 4) glob for **/*.txt under tmp-events, 5) read the file. Then reply with one short line.",
|
|
17
18
|
"type": "send",
|
|
18
19
|
},
|
|
19
20
|
{
|
|
@@ -26,6 +27,7 @@ test('text-only turn: banner, text, typing, footer with context percent', () =>
|
|
|
26
27
|
},
|
|
27
28
|
{
|
|
28
29
|
"blankLineBefore": false,
|
|
30
|
+
"replyTo": null,
|
|
29
31
|
"text": "Done. \`tmp-events/hello.txt\` contains \`hello world\`.",
|
|
30
32
|
"type": "markdown",
|
|
31
33
|
},
|
|
@@ -35,6 +37,7 @@ test('text-only turn: banner, text, typing, footer with context percent', () =>
|
|
|
35
37
|
},
|
|
36
38
|
{
|
|
37
39
|
"agent": "build",
|
|
40
|
+
"channelId": "channel",
|
|
38
41
|
"contextPercent": 6,
|
|
39
42
|
"directory": "/project",
|
|
40
43
|
"durationMs": 17597,
|
|
@@ -55,32 +58,65 @@ test('text-only turn: banner, text, typing, footer with context percent', () =>
|
|
|
55
58
|
expect(view.turn).toBe(null)
|
|
56
59
|
})
|
|
57
60
|
|
|
58
|
-
test('
|
|
61
|
+
test('a turn that posts nothing visible ends without a footer', () => {
|
|
62
|
+
const events = loadFixture('tools.events.jsonl')
|
|
63
|
+
// Read-only tools are hidden at the default verbosity.
|
|
64
|
+
const hiddenTools = new Set(events.flatMap((event) => (event.type === 'session.tool.input.started' && ['read', 'glob'].includes(event.data.name) ? [event.data.id] : [])))
|
|
65
|
+
const quiet = events.filter((event) => {
|
|
66
|
+
if (event.type.startsWith('session.text.')) return false
|
|
67
|
+
if (!event.type.startsWith('session.tool.')) return true
|
|
68
|
+
const data: { readonly [key: string]: JsonValue | undefined } = event.data
|
|
69
|
+
return typeof data['id'] === 'string' && hiddenTools.has(data['id'])
|
|
70
|
+
})
|
|
71
|
+
const footers = (input: ThreadEvent[]) => replay({ events: input }).effects.filter((effect) => effect.type === 'footer').length
|
|
72
|
+
expect(hiddenTools.size).toBe(2)
|
|
73
|
+
expect({ quiet: footers(quiet), normal: footers(events) }).toMatchInlineSnapshot(`
|
|
74
|
+
{
|
|
75
|
+
"normal": 1,
|
|
76
|
+
"quiet": 0,
|
|
77
|
+
}
|
|
78
|
+
`)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('any turn that answers IDLE posts no text and no footer', () => {
|
|
59
82
|
const turn = loadFixture('tools.events.jsonl').filter((event) => !event.type.startsWith('session.tool.'))
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
83
|
+
const idle = turn.map((event) => (event.type === 'session.text.ended' ? { ...event, data: { ...event.data, text: 'IDLE' } } : event))
|
|
84
|
+
const visible = replay({ events: idle }).effects.filter((effect) => effect.type === 'markdown' || effect.type === 'footer')
|
|
85
|
+
expect(visible).toEqual([])
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('a coordinator turn with tool lines but no text (or only IDLE) posts no footer; a thread turn does', () => {
|
|
89
|
+
const events = loadFixture('tools.events.jsonl')
|
|
90
|
+
const idle = events.map((event) => (event.type === 'session.text.ended' ? { ...event, data: { ...event.data, text: 'IDLE' } } : event))
|
|
91
|
+
const silent = events.filter((event) => !event.type.startsWith('session.text.'))
|
|
92
|
+
const sessionId = rootSessionId(events.filter((event): event is V2Event => !event.type.startsWith('kimaki.')))
|
|
93
|
+
const shown = (input: ThreadEvent[], coordinator: boolean) =>
|
|
94
|
+
replay({ events: input, view: emptyView({ sessionId, channelId: 'channel', directory: '/project', isNew: true, coordinator }) }).effects.flatMap((effect) =>
|
|
95
|
+
effect.type === 'footer' ? ['footer'] : effect.type === 'send' && effect.text.includes('┣') ? ['tool'] : [],
|
|
96
|
+
)
|
|
97
|
+
expect({ coordinatorIdle: shown(idle, true), coordinatorNoText: shown(silent, true), threadIdle: shown(idle, false) }).toMatchInlineSnapshot(`
|
|
74
98
|
{
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
99
|
+
"coordinatorIdle": [
|
|
100
|
+
"tool",
|
|
101
|
+
"tool",
|
|
102
|
+
"tool",
|
|
103
|
+
"tool",
|
|
104
|
+
"tool",
|
|
79
105
|
],
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
106
|
+
"coordinatorNoText": [
|
|
107
|
+
"tool",
|
|
108
|
+
"tool",
|
|
109
|
+
"tool",
|
|
110
|
+
"tool",
|
|
111
|
+
"tool",
|
|
112
|
+
],
|
|
113
|
+
"threadIdle": [
|
|
114
|
+
"tool",
|
|
115
|
+
"tool",
|
|
116
|
+
"tool",
|
|
117
|
+
"tool",
|
|
118
|
+
"tool",
|
|
119
|
+
"footer",
|
|
84
120
|
],
|
|
85
121
|
}
|
|
86
122
|
`)
|
|
@@ -100,11 +136,27 @@ test('kimaki sleep in a turn makes its footer silent, the next turn notifies aga
|
|
|
100
136
|
`)
|
|
101
137
|
})
|
|
102
138
|
|
|
139
|
+
test('root model and agent switches after the banner show as subtext lines', () => {
|
|
140
|
+
const events = loadFixture('switch-model.events.jsonl')
|
|
141
|
+
const switches = events.filter((event) => event.type === 'session.model.selected' || event.type === 'session.agent.selected')
|
|
142
|
+
// Before the first step the banner shows the model and agent; a re-select of the same model shows nothing.
|
|
143
|
+
const sameModel = switches.flatMap((event) => (event.type === 'session.model.selected' ? [{ ...event, data: { ...event.data, previous: event.data.model } }] : []))
|
|
144
|
+
const created = events.findIndex((event) => event.type === 'session.created')
|
|
145
|
+
const lines = effectLines(replay({ events: [...events.slice(0, created + 1), ...switches, ...events.slice(created + 1), ...sameModel] }).effects)
|
|
146
|
+
expect(lines.filter((line) => /Switched|using/.test(line))).toMatchInlineSnapshot(`
|
|
147
|
+
[
|
|
148
|
+
"-# *using openai/gpt-6-luna low ⋅ build*",
|
|
149
|
+
"-# Switched model to GPT 6 Luna (medium)",
|
|
150
|
+
"-# Switched agent to Plan",
|
|
151
|
+
]
|
|
152
|
+
`)
|
|
153
|
+
})
|
|
154
|
+
|
|
103
155
|
test('execution failure shows the error and no footer, interrupt shows nothing', () => {
|
|
104
156
|
const events = loadFixture('abort.events.jsonl')
|
|
105
157
|
const sessionID = rootSessionId(events)
|
|
106
158
|
const started = events.find((event) => event.type === 'session.execution.started')!
|
|
107
|
-
const view = emptyView({ sessionId: sessionID, channelId: 'channel', directory: '/project', isNew: false })
|
|
159
|
+
const view = emptyView({ sessionId: sessionID, channelId: 'channel', directory: '/project', isNew: false, coordinator: false })
|
|
108
160
|
const busy = reduce({ view, event: started, prefs: DEFAULT_PREFS }).view
|
|
109
161
|
const failed = reduce({
|
|
110
162
|
view: busy,
|
|
@@ -138,7 +190,8 @@ test('execution failure shows the error and no footer, interrupt shows nothing',
|
|
|
138
190
|
expect(replay({ events, view }).effects).toMatchInlineSnapshot(`
|
|
139
191
|
[
|
|
140
192
|
{
|
|
141
|
-
"
|
|
193
|
+
"echo": true,
|
|
194
|
+
"text": "-# » **user:** Run \`sleep 30 && echo never\` with bash, then summarize.",
|
|
142
195
|
"type": "send",
|
|
143
196
|
},
|
|
144
197
|
{
|
|
@@ -159,7 +212,8 @@ test('execution failure shows the error and no footer, interrupt shows nothing',
|
|
|
159
212
|
"type": "viewed",
|
|
160
213
|
},
|
|
161
214
|
{
|
|
162
|
-
"
|
|
215
|
+
"echo": true,
|
|
216
|
+
"text": "-# » **user:** Say only: resumed after abort.",
|
|
163
217
|
"type": "send",
|
|
164
218
|
},
|
|
165
219
|
{
|
|
@@ -168,6 +222,7 @@ test('execution failure shows the error and no footer, interrupt shows nothing',
|
|
|
168
222
|
},
|
|
169
223
|
{
|
|
170
224
|
"blankLineBefore": false,
|
|
225
|
+
"replyTo": null,
|
|
171
226
|
"text": "resumed after abort",
|
|
172
227
|
"type": "markdown",
|
|
173
228
|
},
|
|
@@ -177,6 +232,7 @@ test('execution failure shows the error and no footer, interrupt shows nothing',
|
|
|
177
232
|
},
|
|
178
233
|
{
|
|
179
234
|
"agent": "build",
|
|
235
|
+
"channelId": "channel",
|
|
180
236
|
"contextPercent": 6,
|
|
181
237
|
"directory": "/project",
|
|
182
238
|
"durationMs": 1387,
|
|
@@ -211,7 +267,7 @@ test('only these event types produce effects for the root session', () => {
|
|
|
211
267
|
for (const file of files) {
|
|
212
268
|
const events = loadFixture(file)
|
|
213
269
|
const sessionId = rootSessionId(events)
|
|
214
|
-
let view = emptyView({ sessionId, channelId: 'channel', directory: '/project', isNew: true })
|
|
270
|
+
let view = emptyView({ sessionId, channelId: 'channel', directory: '/project', isNew: true, coordinator: false })
|
|
215
271
|
for (const event of events) {
|
|
216
272
|
const result = reduce({ view, event, prefs: DEFAULT_PREFS })
|
|
217
273
|
view = result.view
|
|
@@ -220,11 +276,13 @@ test('only these event types produce effects for the root session', () => {
|
|
|
220
276
|
}
|
|
221
277
|
expect([...producing].sort()).toMatchInlineSnapshot(`
|
|
222
278
|
[
|
|
279
|
+
"session.agent.selected",
|
|
223
280
|
"session.execution.interrupted",
|
|
224
281
|
"session.execution.started",
|
|
225
282
|
"session.execution.succeeded",
|
|
226
283
|
"session.inbox.delivered",
|
|
227
284
|
"session.inbox.enqueued",
|
|
285
|
+
"session.model.selected",
|
|
228
286
|
"session.shell.ended",
|
|
229
287
|
"session.shell.started",
|
|
230
288
|
"session.step.started",
|
|
@@ -244,14 +302,12 @@ test('only these event types produce effects for the root session', () => {
|
|
|
244
302
|
"provider.updated",
|
|
245
303
|
"reference.updated",
|
|
246
304
|
"server.connected",
|
|
247
|
-
"session.agent.selected",
|
|
248
305
|
"session.compaction.delta",
|
|
249
306
|
"session.compaction.ended",
|
|
250
307
|
"session.compaction.started",
|
|
251
308
|
"session.created",
|
|
252
309
|
"session.forked",
|
|
253
310
|
"session.instructions.updated",
|
|
254
|
-
"session.model.selected",
|
|
255
311
|
"session.renamed",
|
|
256
312
|
"session.step.ended",
|
|
257
313
|
"session.step.failed",
|
|
@@ -277,7 +333,7 @@ test('only these event types produce effects for the root session', () => {
|
|
|
277
333
|
test('retry notices are throttled to one per 10s', () => {
|
|
278
334
|
const events = loadFixture('abort.events.jsonl')
|
|
279
335
|
const sessionID = rootSessionId(events)
|
|
280
|
-
const view = emptyView({ sessionId: sessionID, channelId: 'channel', directory: '/project', isNew: false })
|
|
336
|
+
const view = emptyView({ sessionId: sessionID, channelId: 'channel', directory: '/project', isNew: false, coordinator: false })
|
|
281
337
|
const retry = (created: number, attempt: number): V2Event => ({
|
|
282
338
|
id: `evt_retry_${attempt}`,
|
|
283
339
|
created,
|
|
@@ -294,8 +350,8 @@ test('retry notices are throttled to one per 10s', () => {
|
|
|
294
350
|
const { effects } = replay({ events: [retry(1_000, 1), retry(6_000, 2), retry(12_000, 3)], view })
|
|
295
351
|
expect(effectLines(effects)).toMatchInlineSnapshot(`
|
|
296
352
|
[
|
|
297
|
-
"-#
|
|
298
|
-
"-#
|
|
353
|
+
"-# retrying in 5s (attempt 1): rate limited",
|
|
354
|
+
"-# retrying in 5s (attempt 3): rate limited",
|
|
299
355
|
]
|
|
300
356
|
`)
|
|
301
357
|
})
|
|
@@ -308,40 +364,133 @@ test('cache miss notice when the cached prefix shrinks, e.g. after the cache TTL
|
|
|
308
364
|
const last = events[lastIndex]
|
|
309
365
|
if (last?.type !== 'session.step.ended') return expect.fail('fixture has no root step.ended')
|
|
310
366
|
// Same turn, but the last request found nothing in cache 10 minutes later.
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
367
|
+
const withCache = (cache: { read: number; write: number }, input = last.data.tokens.input) =>
|
|
368
|
+
events.map((event, index) =>
|
|
369
|
+
index === lastIndex ? { ...last, created: last.created + 600_000, data: { ...last.data, tokens: { ...last.data.tokens, input, cache } } } : event,
|
|
370
|
+
)
|
|
371
|
+
const cacheLines = (stream: V2Event[]) => effectLines(replay({ events: stream }).effects).filter((line) => line.includes('cache'))
|
|
372
|
+
// Everything was written again: a full loss.
|
|
373
|
+
expect(cacheLines(withCache({ read: 0, write: 25_000 }, 0))).toMatchInlineSnapshot(`
|
|
315
374
|
[
|
|
316
|
-
"\\n-#
|
|
375
|
+
"\\n-# prompt cache miss: 0 of 25k tokens cached, 10m 1s after the last request",
|
|
317
376
|
]
|
|
318
377
|
`)
|
|
378
|
+
// Less than half of the prompt was written: a partial miss shows nothing.
|
|
379
|
+
expect(cacheLines(withCache({ read: 0, write: 5_000 }, 20_000))).toEqual([])
|
|
319
380
|
expect(effectLines(replay({ events }).effects).filter((line) => line.includes('cache'))).toEqual([])
|
|
320
381
|
})
|
|
321
382
|
|
|
383
|
+
test('compaction posts the new context size from the next root step', () => {
|
|
384
|
+
const events = loadFixture('tools.events.jsonl')
|
|
385
|
+
const sessionID = rootSessionId(events)
|
|
386
|
+
const lastIndex = events.findLastIndex((event) => event.type === 'session.step.ended' && event.data.sessionID === sessionID)
|
|
387
|
+
const compacted: V2Event = {
|
|
388
|
+
id: 'evt_compacted',
|
|
389
|
+
created: 0,
|
|
390
|
+
type: 'session.compaction.ended',
|
|
391
|
+
durable: { aggregateID: sessionID, seq: 0, version: 1 },
|
|
392
|
+
data: { sessionID, reason: 'auto', text: '## Objective', recent: '' },
|
|
393
|
+
}
|
|
394
|
+
const withCompaction = [...events.slice(0, lastIndex), compacted, ...events.slice(lastIndex)]
|
|
395
|
+
expect(effectLines(replay({ events: withCompaction }).effects).filter((line) => line.includes('compacted'))).toMatchInlineSnapshot(`
|
|
396
|
+
[
|
|
397
|
+
"\\n-# context compacted to 26k tokens (6%)",
|
|
398
|
+
]
|
|
399
|
+
`)
|
|
400
|
+
// `/compact` alone: the execution ends with no step after it.
|
|
401
|
+
const end = events.findLastIndex((event) => event.type === 'session.execution.succeeded')
|
|
402
|
+
const manual = [...events.slice(0, end), compacted, ...events.slice(end)]
|
|
403
|
+
expect(effectLines(replay({ events: manual }).effects).filter((line) => line.includes('compacted'))).toMatchInlineSnapshot(`
|
|
404
|
+
[
|
|
405
|
+
"\\n-# context compacted",
|
|
406
|
+
]
|
|
407
|
+
`)
|
|
408
|
+
})
|
|
409
|
+
|
|
322
410
|
test('prompts from other clients echo, except the expanded prompt of a command Kimaki ran', () => {
|
|
323
411
|
const events = loadFixture('queue-plain.events.jsonl')
|
|
324
|
-
|
|
325
|
-
const
|
|
412
|
+
// Queued ones show in their ack (`[show queue:...]`), a reply to it marks the run.
|
|
413
|
+
const echoes = (before: ThreadEvent[]) =>
|
|
414
|
+
effectLines(replay({ events: [...before, ...events] }).effects).filter((line) => line.includes('» ') || line.startsWith('[reply to'))
|
|
415
|
+
const command = { type: 'kimaki.command' as const, text: '/review auth', username: 'tommy', messageId: null, echo: null }
|
|
326
416
|
expect(echoes([])).toMatchInlineSnapshot(`
|
|
327
417
|
[
|
|
328
|
-
"» **user:** Run \`sleep 6 && echo slow-done\` with bash, then say done.",
|
|
329
|
-
"» **user:** QUEUED-1: reply with the word apple.
|
|
330
|
-
|
|
418
|
+
"-# » **user:** Run \`sleep 6 && echo slow-done\` with bash, then say done.",
|
|
419
|
+
"[show queue:msg_0f20347e1001nsJ7W4hOvhBpQb] -# » **user:** QUEUED-1: reply with the word apple.
|
|
420
|
+
-# Queued at position 1. Remove it with /clear-queue position:1",
|
|
421
|
+
"[show queue:msg_0f20347e500196F4o0voD7LGMV] -# » **user:** QUEUED-2: reply with the word cherry.
|
|
422
|
+
-# Queued at position 2. Remove it with /clear-queue position:2",
|
|
423
|
+
"[edit queue:msg_0f20347e1001nsJ7W4hOvhBpQb] -# » **user:** QUEUED-1: reply with the word apple.",
|
|
424
|
+
"[reply to queue:msg_0f20347e1001nsJ7W4hOvhBpQb] -# Queued message sent",
|
|
425
|
+
"[edit queue:msg_0f20347e500196F4o0voD7LGMV] -# » **user:** QUEUED-2: reply with the word cherry.",
|
|
426
|
+
"[reply to queue:msg_0f20347e500196F4o0voD7LGMV] -# Queued message sent",
|
|
331
427
|
]
|
|
332
428
|
`)
|
|
333
|
-
// The first prompt is the command's template: hidden. The queued ones still
|
|
429
|
+
// The first prompt is the command's template: hidden. The queued ones still show.
|
|
334
430
|
expect(echoes([command])).toMatchInlineSnapshot(`
|
|
335
431
|
[
|
|
336
|
-
"» **user:** QUEUED-1: reply with the word apple.
|
|
337
|
-
|
|
432
|
+
"[show queue:msg_0f20347e1001nsJ7W4hOvhBpQb] -# » **user:** QUEUED-1: reply with the word apple.
|
|
433
|
+
-# Queued at position 1. Remove it with /clear-queue position:1",
|
|
434
|
+
"[show queue:msg_0f20347e500196F4o0voD7LGMV] -# » **user:** QUEUED-2: reply with the word cherry.
|
|
435
|
+
-# Queued at position 2. Remove it with /clear-queue position:2",
|
|
436
|
+
"[edit queue:msg_0f20347e1001nsJ7W4hOvhBpQb] -# » **user:** QUEUED-1: reply with the word apple.",
|
|
437
|
+
"[reply to queue:msg_0f20347e1001nsJ7W4hOvhBpQb] -# Queued message sent",
|
|
438
|
+
"[edit queue:msg_0f20347e500196F4o0voD7LGMV] -# » **user:** QUEUED-2: reply with the word cherry.",
|
|
439
|
+
"[reply to queue:msg_0f20347e500196F4o0voD7LGMV] -# Queued message sent",
|
|
338
440
|
]
|
|
339
441
|
`)
|
|
442
|
+
// A command sent with `kimaki send` has no Discord message: its echo stands in for the template.
|
|
443
|
+
expect(echoes([{ ...command, echo: '-# » **CLI:** /review auth' }])[0]).toMatchInlineSnapshot(`"-# » **CLI:** /review auth"`)
|
|
340
444
|
// A failed command leaves nothing pending.
|
|
341
445
|
expect(echoes([command, { type: 'kimaki.command-failed' }])).toEqual(echoes([]))
|
|
342
446
|
})
|
|
343
447
|
|
|
344
|
-
test('
|
|
448
|
+
test('coordinator: a prompt reply goes to its Discord message, a queued one gets "Queued message sent" even when it answers IDLE', () => {
|
|
449
|
+
// The fixture's prompts get Discord metadata, like prompt.ts writes it: messages 101, 102, 103.
|
|
450
|
+
const withDiscord = (threadId: string) => {
|
|
451
|
+
let next = 101
|
|
452
|
+
return loadFixture('queue-plain.events.jsonl').map((event): V2Event => {
|
|
453
|
+
if (event.type !== 'session.inbox.enqueued' || event.data.item.type !== 'user') return event
|
|
454
|
+
const discord = { messageId: String(next++), threadId, username: 'tommy' }
|
|
455
|
+
const item = { ...event.data.item, payload: { ...event.data.item.payload, metadata: { discord } } }
|
|
456
|
+
return { ...event, data: { ...event.data, item } }
|
|
457
|
+
})
|
|
458
|
+
}
|
|
459
|
+
// replay() binds the view to channel `channel`: a prompt whose conversation is that channel is a coordinator prompt.
|
|
460
|
+
expect(effectLines(replay({ events: withDiscord('channel') }).effects)).toMatchInlineSnapshot(`
|
|
461
|
+
[
|
|
462
|
+
"[typing on]",
|
|
463
|
+
"-# *using openai/gpt-6-luna low ⋅ build*",
|
|
464
|
+
"-# ┣ shell _sleep 6 && echo slow-done_",
|
|
465
|
+
"[show queue:msg_0f20347e1001nsJ7W4hOvhBpQb reply to 102] -# Queued at position 1. Delete the original message to remove it, or use /clear-queue position:1",
|
|
466
|
+
"[show queue:msg_0f20347e500196F4o0voD7LGMV reply to 103] -# Queued at position 2. Delete the original message to remove it, or use /clear-queue position:2",
|
|
467
|
+
"[reply to 101] Done.",
|
|
468
|
+
"[silent] -# *main ⋅ 10s ⋅ 6% ⋅ gpt-6-luna*",
|
|
469
|
+
"[edit queue:msg_0f20347e1001nsJ7W4hOvhBpQb] -# Queued",
|
|
470
|
+
"[reply to 102] -# Queued message sent",
|
|
471
|
+
"apple",
|
|
472
|
+
"[silent] -# *main ⋅ 2s ⋅ 6% ⋅ gpt-6-luna*",
|
|
473
|
+
"[edit queue:msg_0f20347e500196F4o0voD7LGMV] -# Queued",
|
|
474
|
+
"[reply to 103] -# Queued message sent",
|
|
475
|
+
"cherry",
|
|
476
|
+
"[typing off]",
|
|
477
|
+
"-# *main ⋅ 2s ⋅ 6% ⋅ gpt-6-luna*",
|
|
478
|
+
]
|
|
479
|
+
`)
|
|
480
|
+
// A coordinator that starts a worker answers IDLE: the queued prompt still shows that it started.
|
|
481
|
+
const idle = withDiscord('channel').map((event) => (event.type === 'session.text.ended' && event.data.text.trim() === 'apple' ? { ...event, data: { ...event.data, text: 'IDLE' } } : event))
|
|
482
|
+
expect(effectLines(replay({ events: idle }).effects).filter((line) => line.includes('queue:msg_0f20347e1001') || line.startsWith('[reply to 102]'))).toMatchInlineSnapshot(`
|
|
483
|
+
[
|
|
484
|
+
"[show queue:msg_0f20347e1001nsJ7W4hOvhBpQb reply to 102] -# Queued at position 1. Delete the original message to remove it, or use /clear-queue position:1",
|
|
485
|
+
"[edit queue:msg_0f20347e1001nsJ7W4hOvhBpQb] -# Queued",
|
|
486
|
+
"[reply to 102] -# Queued message sent",
|
|
487
|
+
]
|
|
488
|
+
`)
|
|
489
|
+
const thread = replay({ events: withDiscord('thread') }).effects
|
|
490
|
+
expect(thread.filter((effect) => effect.type === 'markdown').map((effect) => effect.replyTo)).toEqual([null, null, null])
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
test('snapshot marks an item promoted while disconnected as started, without an echo', () => {
|
|
345
494
|
const events = loadFixture('queue-plain.events.jsonl')
|
|
346
495
|
const index = events.findIndex((event) => event.type === 'session.inbox.enqueued' && event.data.item.delivery === 'queue')
|
|
347
496
|
const enqueued = events[index]
|
|
@@ -363,7 +512,8 @@ test('snapshot closes the ack of an item promoted while disconnected, without an
|
|
|
363
512
|
const hydrated = reduce({ view, event: snapshot, prefs: DEFAULT_PREFS })
|
|
364
513
|
expect(effectLines(hydrated.effects)).toMatchInlineSnapshot(`
|
|
365
514
|
[
|
|
366
|
-
"[edit queue:msg_0f20347e1001nsJ7W4hOvhBpQb] -#
|
|
515
|
+
"[edit queue:msg_0f20347e1001nsJ7W4hOvhBpQb] -# » **user:** QUEUED-1: reply with the word apple.",
|
|
516
|
+
"[reply to queue:msg_0f20347e1001nsJ7W4hOvhBpQb] -# Queued message sent",
|
|
367
517
|
]
|
|
368
518
|
`)
|
|
369
519
|
expect(hydrated.effects.map((effect) => effect.type)).toEqual(['edit'])
|
|
@@ -374,7 +524,7 @@ test('snapshot closes the ack of an item promoted while disconnected, without an
|
|
|
374
524
|
test('execute: final metadata shows inner calls whose progress was missed, then failures', () => {
|
|
375
525
|
const events = loadFixture('abort.events.jsonl')
|
|
376
526
|
const sessionID = rootSessionId(events)
|
|
377
|
-
const view = emptyView({ sessionId: sessionID, channelId: 'channel', directory: '/project', isNew: false })
|
|
527
|
+
const view = emptyView({ sessionId: sessionID, channelId: 'channel', directory: '/project', isNew: false, coordinator: false })
|
|
378
528
|
const base = { created: 1_000 }
|
|
379
529
|
const durable = <V extends number>(version: V) => ({ aggregateID: sessionID, seq: 1, version })
|
|
380
530
|
const call = { sessionID, assistantMessageID: 'msg_a', id: 'call_1' }
|