kimaki 1.4.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 +10 -1
- package/dist/bot.js +14 -6
- package/dist/bot.js.map +1 -1
- package/dist/btw.e2e.test.js +3 -3
- package/dist/builtin-skills.d.ts +1 -1
- package/dist/builtin-skills.js +1 -1
- package/dist/builtin-skills.js.map +1 -1
- package/dist/builtin-skills.test.js +3 -4
- package/dist/builtin-skills.test.js.map +1 -1
- package/dist/catalog-refresh.e2e.test.js +2 -2
- package/dist/cli/bot.js +25 -14
- 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 +1 -1
- package/dist/event-loop.js +73 -43
- 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 +4 -2
- package/dist/main.js +13 -3
- 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 +4 -4
- package/dist/markdown/render-markdown.js +6 -5
- 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 +76 -17
- 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 +166 -22
- 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 +174 -63
- 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 +3 -1
- package/dist/system-prompt.js +138 -64
- 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 -1
- package/dist/test/harness.js +51 -10
- package/dist/test/harness.js.map +1 -1
- package/dist/test/replay.js +11 -7
- package/dist/test/replay.js.map +1 -1
- package/dist/thread-reducer.d.ts +29 -3
- package/dist/thread-reducer.fixtures.test.js +58 -51
- package/dist/thread-reducer.fixtures.test.js.map +1 -1
- package/dist/thread-reducer.js +112 -25
- package/dist/thread-reducer.js.map +1 -1
- package/dist/thread-reducer.test.js +197 -24
- package/dist/thread-reducer.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 +12 -6
- 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 +29 -5
- package/src/btw.e2e.test.ts +3 -3
- package/src/builtin-skills.test.ts +3 -4
- package/src/builtin-skills.ts +1 -1
- package/src/catalog-refresh.e2e.test.ts +2 -2
- package/src/cli/bot.ts +23 -14
- 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 -42
- 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 +22 -7
- package/src/markdown/markdown.test.ts +4 -4
- package/src/markdown/render-markdown.ts +6 -5
- 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 +68 -15
- 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 +160 -21
- package/src/session-ops.e2e.test.ts +64 -11
- package/src/sessions.ts +214 -64
- 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 +143 -63
- package/src/test/generate-log-plugin/index.ts +16 -0
- package/src/test/harness.ts +77 -14
- package/src/test/replay.ts +12 -6
- package/src/thread-reducer.fixtures.test.ts +58 -51
- package/src/thread-reducer.test.ts +207 -26
- package/src/thread-reducer.ts +138 -37
- 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/test/harness.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// temp root with private XDG dirs and SQLite DB, started with the same
|
|
3
3
|
// `serve --service` command the user's service uses, so the bot connects
|
|
4
4
|
// through Service.discover() exactly like production. The bot runs in-process
|
|
5
|
-
// against discord-digital-twin.
|
|
5
|
+
// against discord-digital-twin. The model is opencode-deterministic-provider,
|
|
6
|
+
// answered by startTestModel() in this process.
|
|
6
7
|
|
|
7
8
|
import { execFile, execFileSync, spawn, type ChildProcess } from 'node:child_process'
|
|
8
9
|
import { promisify } from 'node:util'
|
|
@@ -22,10 +23,13 @@ import { ChannelType, ComponentType, type APIMessage } from 'discord.js'
|
|
|
22
23
|
import { DigitalDiscord } from 'discord-digital-twin/src'
|
|
23
24
|
import {
|
|
24
25
|
buildDeterministicOpencode2Config,
|
|
26
|
+
getLatestUserText,
|
|
27
|
+
resolveResponse,
|
|
25
28
|
type DeterministicMatcher,
|
|
26
29
|
} from 'opencode-deterministic-provider'
|
|
27
30
|
|
|
28
31
|
import { disabledAnalytics, type Analytics } from '../analytics.ts'
|
|
32
|
+
import type { WarmupTimers } from '../bot.ts'
|
|
29
33
|
import { GATEWAY_APP_ID, saveCredentials, type Credentials } from '../credentials.ts'
|
|
30
34
|
import { openDb, verbosityToV1, type Verbosity } from '../db.ts'
|
|
31
35
|
import type { ToolInput } from '../format-parts.ts'
|
|
@@ -60,8 +64,50 @@ export function opencodeBinary(): string {
|
|
|
60
64
|
return path.join(path.dirname(packageJsonPath), 'bin/opencode.exe')
|
|
61
65
|
}
|
|
62
66
|
|
|
67
|
+
export type TestModelRequest = {
|
|
68
|
+
latestUserText: string
|
|
69
|
+
// The whole prompt as JSON: system, history and tool results.
|
|
70
|
+
raw: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// The deterministic model, answered in this process: OpenCode's provider POSTs each
|
|
74
|
+
// prompt here. Tests read `requests` to check what the model saw and may push matchers.
|
|
75
|
+
export type TestModel = {
|
|
76
|
+
matchers: DeterministicMatcher[]
|
|
77
|
+
requests: TestModelRequest[]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function startTestModel({ matchers }: { matchers: DeterministicMatcher[] }) {
|
|
81
|
+
const model: TestModel = { matchers, requests: [] }
|
|
82
|
+
const server = http.createServer((request, response) => {
|
|
83
|
+
const chunks: Buffer[] = []
|
|
84
|
+
request.on('data', (chunk: Buffer) => chunks.push(chunk))
|
|
85
|
+
request.on('end', () => {
|
|
86
|
+
try {
|
|
87
|
+
const { prompt } = JSON.parse(Buffer.concat(chunks).toString('utf8')) as Parameters<typeof resolveResponse>[0]
|
|
88
|
+
model.requests.push({ latestUserText: getLatestUserText({ prompt }), raw: JSON.stringify(prompt) })
|
|
89
|
+
response.setHeader('content-type', 'application/json')
|
|
90
|
+
response.end(JSON.stringify(resolveResponse({ prompt, settings: { strict: false, matchers: model.matchers } })))
|
|
91
|
+
} catch (error) {
|
|
92
|
+
// The provider turns this into a model error, so the run fails visibly instead of hanging.
|
|
93
|
+
response.statusCode = 500
|
|
94
|
+
response.end(error instanceof Error ? error.stack : String(error))
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve))
|
|
99
|
+
const address = server.address()
|
|
100
|
+
if (!address || typeof address !== 'object') throw new Error('test model has no port')
|
|
101
|
+
return {
|
|
102
|
+
model,
|
|
103
|
+
url: `http://127.0.0.1:${address.port}`,
|
|
104
|
+
stop: () => new Promise<void>((resolve) => server.close(() => resolve())),
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
63
108
|
export type OpencodeTestServer = {
|
|
64
109
|
root: string
|
|
110
|
+
model: TestModel
|
|
65
111
|
serviceFile: string
|
|
66
112
|
// Global OpenCode config dir of this server (XDG_CONFIG_HOME/opencode).
|
|
67
113
|
configDir: string
|
|
@@ -91,7 +137,7 @@ export async function startOpencodeTestServer({
|
|
|
91
137
|
// Appended after the allow-all rules: the last matching rule wins.
|
|
92
138
|
permissions?: Array<{ action: string; resource: string; effect: 'allow' | 'deny' | 'ask' }>
|
|
93
139
|
// More deterministic models next to TEST_MODEL, e.g. with thinking variants.
|
|
94
|
-
models?: Record<string, { name: string; variants?: Array<{ id: string }
|
|
140
|
+
models?: Record<string, { name: string; variants?: Array<{ id: string }>; limit?: { context?: number; output?: number } }>
|
|
95
141
|
// Agent overrides and custom agents (config `agents`).
|
|
96
142
|
agents?: Record<string, { description?: string; mode?: 'primary' | 'subagent' | 'all'; hidden?: boolean; model?: string }>
|
|
97
143
|
// MCP servers (config `mcp.servers`).
|
|
@@ -104,9 +150,10 @@ export async function startOpencodeTestServer({
|
|
|
104
150
|
fs.mkdirSync(projectDirectory, { recursive: true })
|
|
105
151
|
execFileSync('git', ['init', '-q', '-b', 'main'], { cwd: projectDirectory })
|
|
106
152
|
const serviceFile = path.join(root, 'state', 'opencode', 'service.json')
|
|
153
|
+
const testModel = await startTestModel({ matchers })
|
|
107
154
|
const config = buildDeterministicOpencode2Config({
|
|
108
155
|
model: TEST_MODEL,
|
|
109
|
-
settings: {
|
|
156
|
+
settings: { responderUrl: testModel.url },
|
|
110
157
|
permissions: [
|
|
111
158
|
{ action: 'shell', resource: '*', effect: 'allow' },
|
|
112
159
|
{ action: 'edit', resource: '*', effect: 'allow' },
|
|
@@ -183,6 +230,7 @@ export async function startOpencodeTestServer({
|
|
|
183
230
|
await start()
|
|
184
231
|
return {
|
|
185
232
|
root,
|
|
233
|
+
model: testModel.model,
|
|
186
234
|
serviceFile,
|
|
187
235
|
configDir: path.join(env.XDG_CONFIG_HOME, 'opencode'),
|
|
188
236
|
dbFile: path.join(env.XDG_DATA_HOME, 'opencode', 'opencode.db'),
|
|
@@ -193,6 +241,7 @@ export async function startOpencodeTestServer({
|
|
|
193
241
|
start,
|
|
194
242
|
stop: async () => {
|
|
195
243
|
await kill()
|
|
244
|
+
await testModel.stop()
|
|
196
245
|
fs.rmSync(root, { recursive: true, force: true })
|
|
197
246
|
},
|
|
198
247
|
}
|
|
@@ -499,6 +548,8 @@ export async function startTestBot({
|
|
|
499
548
|
analytics,
|
|
500
549
|
clock,
|
|
501
550
|
schedulerIntervalMs,
|
|
551
|
+
coordinator,
|
|
552
|
+
warmupTimers,
|
|
502
553
|
}: {
|
|
503
554
|
dataDir: string
|
|
504
555
|
twin: TestTwin
|
|
@@ -519,6 +570,9 @@ export async function startTestBot({
|
|
|
519
570
|
// Scheduling tests pass manualClock() and null, then call bot.scheduler.runDueTasks().
|
|
520
571
|
clock?: Clock
|
|
521
572
|
schedulerIntervalMs?: number | null
|
|
573
|
+
// Default off (`--coordinator false`) so most tests get threads; coordinator tests pass true.
|
|
574
|
+
coordinator?: boolean
|
|
575
|
+
warmupTimers?: WarmupTimers
|
|
522
576
|
}): Promise<BotHandle> {
|
|
523
577
|
// What credential resolution saves in production; `kimaki project add` reads it.
|
|
524
578
|
const credentials: Credentials = twin.discord.botToken.includes(':')
|
|
@@ -535,7 +589,7 @@ export async function startTestBot({
|
|
|
535
589
|
if (lock instanceof Error) throw lock
|
|
536
590
|
const bot = await startBot({
|
|
537
591
|
dataDir,
|
|
538
|
-
|
|
592
|
+
kimakiArgv: [process.execPath, '--import', createRequire(import.meta.url).resolve('tsx'), path.resolve('src/cli.ts')],
|
|
539
593
|
token: credentials.token,
|
|
540
594
|
appId: credentials.appId,
|
|
541
595
|
lock,
|
|
@@ -548,6 +602,8 @@ export async function startTestBot({
|
|
|
548
602
|
...(clock && { clock }),
|
|
549
603
|
...(schedulerIntervalMs !== undefined && { schedulerIntervalMs }),
|
|
550
604
|
...(realtimeBaseUrls && { realtimeBaseUrls }),
|
|
605
|
+
coordinator: coordinator ?? false,
|
|
606
|
+
...(warmupTimers && { warmupTimers }),
|
|
551
607
|
transcriptionBaseUrls: {
|
|
552
608
|
...(geminiBaseUrl && { gemini: geminiBaseUrl }),
|
|
553
609
|
...(openaiBaseUrl && { openai: openaiBaseUrl }),
|
|
@@ -620,7 +676,8 @@ export function schedulingKit({ suite, dataDir, clock }: {
|
|
|
620
676
|
}
|
|
621
677
|
}
|
|
622
678
|
|
|
623
|
-
|
|
679
|
+
// delayMs: how long the fake Gemini waits before it answers (not sent back as a tool argument).
|
|
680
|
+
export type FakeTranscription = { transcription: string; route?: string; agent?: string; delayMs?: number }
|
|
624
681
|
|
|
625
682
|
// A voice message whose audio bytes are the transcription the fake returns.
|
|
626
683
|
export function voiceUrl(result: FakeTranscription): string {
|
|
@@ -650,13 +707,15 @@ export async function startFakeGemini(): Promise<FakeGemini> {
|
|
|
650
707
|
const declaration = body.tools[0]!.functionDeclarations[0]!
|
|
651
708
|
const audio = body.contents[0]!.parts.find((part) => part.inlineData)!.inlineData!
|
|
652
709
|
requests.push({ routes: declaration.parameters.properties.route?.enum ?? null, mimeType: audio.mimeType })
|
|
653
|
-
const args = JSON.parse(Buffer.from(audio.data, 'base64').toString('utf8')) as FakeTranscription
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
710
|
+
const { delayMs = 0, ...args } = JSON.parse(Buffer.from(audio.data, 'base64').toString('utf8')) as FakeTranscription
|
|
711
|
+
setTimeout(() => {
|
|
712
|
+
response.setHeader('content-type', 'application/json')
|
|
713
|
+
response.end(
|
|
714
|
+
JSON.stringify({
|
|
715
|
+
candidates: [{ finishReason: 'STOP', content: { parts: [{ functionCall: { name: declaration.name, args } }] } }],
|
|
716
|
+
}),
|
|
717
|
+
)
|
|
718
|
+
}, delayMs)
|
|
660
719
|
})
|
|
661
720
|
})
|
|
662
721
|
const port = await freePort()
|
|
@@ -727,14 +786,15 @@ export async function startFakeOpenAI({ result }: { result: FakeTranscription })
|
|
|
727
786
|
|
|
728
787
|
export type FakeChatGPT = {
|
|
729
788
|
baseUrl: string
|
|
730
|
-
// Each POST
|
|
731
|
-
requests: Array<{ authorization: string | null; accountId: string | null; filename: string; type: string; audio: Buffer }>
|
|
789
|
+
// Each POST, as the server received it.
|
|
790
|
+
requests: Array<{ path: string; authorization: string | null; accountId: string | null; model: string | null; filename: string; type: string; audio: Buffer }>
|
|
732
791
|
// Reply status; tests set 401 to check the API key fallback.
|
|
733
792
|
status: number
|
|
734
793
|
stop: () => Promise<void>
|
|
735
794
|
}
|
|
736
795
|
|
|
737
796
|
// chatgpt.com/backend-api/transcribe: multipart `file` in, `{ text }` out.
|
|
797
|
+
// Also a local Whisper server (/audio/transcriptions), which takes the same shape plus `model`.
|
|
738
798
|
export async function startFakeChatGPT({ text }: { text: string }): Promise<FakeChatGPT> {
|
|
739
799
|
const fake: Omit<FakeChatGPT, 'baseUrl' | 'stop'> = { requests: [], status: 200 }
|
|
740
800
|
const server = http.createServer((request, response) => {
|
|
@@ -745,9 +805,12 @@ export async function startFakeChatGPT({ text }: { text: string }): Promise<Fake
|
|
|
745
805
|
const form = await new Request('http://fake/transcribe', { method: 'POST', headers, body: Buffer.concat(chunks) }).formData()
|
|
746
806
|
const file = form.get('file')
|
|
747
807
|
if (!(file instanceof File)) throw new Error('fake ChatGPT: no file field')
|
|
808
|
+
const model = form.get('model')
|
|
748
809
|
fake.requests.push({
|
|
810
|
+
path: request.url ?? '',
|
|
749
811
|
authorization: headers.get('authorization'),
|
|
750
812
|
accountId: headers.get('chatgpt-account-id'),
|
|
813
|
+
model: typeof model === 'string' ? model : null,
|
|
751
814
|
filename: file.name,
|
|
752
815
|
type: file.type,
|
|
753
816
|
audio: Buffer.from(await file.arrayBuffer()),
|
package/src/test/replay.ts
CHANGED
|
@@ -25,9 +25,11 @@ export function rootSessionId(events: V2Event[]): string {
|
|
|
25
25
|
return created.data.sessionID
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
const REPLAY_PROJECT = '/project'
|
|
29
|
+
|
|
28
30
|
export const DEFAULT_PREFS: Prefs = {
|
|
29
31
|
verbosity: 'tools',
|
|
30
|
-
|
|
32
|
+
models: { 'openai/gpt-6-luna': { name: 'GPT 6 Luna', context: 400_000 } },
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
export function replay({
|
|
@@ -44,8 +46,9 @@ export function replay({
|
|
|
44
46
|
emptyView({
|
|
45
47
|
sessionId: rootSessionId(events.filter((event): event is V2Event => !event.type.startsWith('kimaki.'))),
|
|
46
48
|
channelId: 'channel',
|
|
47
|
-
directory:
|
|
49
|
+
directory: REPLAY_PROJECT,
|
|
48
50
|
isNew: true,
|
|
51
|
+
coordinator: false,
|
|
49
52
|
})
|
|
50
53
|
return events.reduce<{ view: ThreadView; effects: Effect[] }>(
|
|
51
54
|
(acc, event) => {
|
|
@@ -74,15 +77,18 @@ export function effectLines(effects: Effect[]): string[] {
|
|
|
74
77
|
if (effect.type === 'viewed') return []
|
|
75
78
|
if (effect.type === 'typing') return `[typing ${effect.on ? 'on' : 'off'}]`
|
|
76
79
|
if (effect.type === 'attachments') return `[attachments ${effect.files.map((file) => file.name).join(', ')}]`
|
|
77
|
-
if (effect.type === 'markdown') return `${effect.blankLineBefore ? '\\n' : ''}${effect.text}`
|
|
80
|
+
if (effect.type === 'markdown') return `${effect.replyTo ? `[reply to ${effect.replyTo}] ` : ''}${effect.blankLineBefore ? '\\n' : ''}${effect.text}`
|
|
78
81
|
if (effect.type === 'show') {
|
|
79
82
|
const reply = effect.replyTo ? ` reply to ${effect.replyTo}` : ''
|
|
80
83
|
return `[show ${effect.key}${reply}] ${effect.messages.map(uiLine).join(' | ')}`
|
|
81
84
|
}
|
|
82
|
-
if (effect.type === 'edit')
|
|
83
|
-
|
|
85
|
+
if (effect.type === 'edit') {
|
|
86
|
+
const reply = effect.reply === undefined ? [] : [`[reply to ${effect.replyTo ?? effect.key}] ${effect.reply}`]
|
|
87
|
+
return [`[edit ${effect.key}] ${effect.messages.map(uiLine).join(' | ')}`, ...reply]
|
|
88
|
+
}
|
|
89
|
+
// Tests have no git checkout: the branch is always main. The channel runs build in /project.
|
|
84
90
|
if (effect.type === 'footer') {
|
|
85
|
-
const footer = formatFooter({ ...effect,
|
|
91
|
+
const footer = formatFooter({ ...effect, branch: 'main', defaultBranch: null, projectDirectory: REPLAY_PROJECT, defaultAgent: 'build' })
|
|
86
92
|
return effect.notify ? footer : `[silent] ${footer}`
|
|
87
93
|
}
|
|
88
94
|
return effect.text.replace(/^\n/, '\\n')
|
|
@@ -11,7 +11,7 @@ test('tools: names from tool.input.started, failed tool lines, read-only tools h
|
|
|
11
11
|
const { effects } = replay({ events: loadFixture('tools.events.jsonl') })
|
|
12
12
|
expect(effectLines(effects)).toMatchInlineSnapshot(`
|
|
13
13
|
[
|
|
14
|
-
"» **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.",
|
|
14
|
+
"-# » **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.",
|
|
15
15
|
"[typing on]",
|
|
16
16
|
"-# *using openai/gpt-6-luna low ⋅ build*",
|
|
17
17
|
"-# ┣ shell _ls cli/src \\| head -5_",
|
|
@@ -23,7 +23,7 @@ test('tools: names from tool.input.started, failed tool lines, read-only tools h
|
|
|
23
23
|
"-# ┣ shell _python3 -c "from pathlib import Path; p=Path('tmp-events/hello.txt'); p.write\\_t…_",
|
|
24
24
|
"\\nDone. \`tmp-events/hello.txt\` contains \`hello world\`.",
|
|
25
25
|
"[typing off]",
|
|
26
|
-
"-# *
|
|
26
|
+
"-# *main ⋅ 17s ⋅ 6% ⋅ gpt-6-luna*",
|
|
27
27
|
]
|
|
28
28
|
`)
|
|
29
29
|
})
|
|
@@ -31,18 +31,18 @@ test('tools: names from tool.input.started, failed tool lines, read-only tools h
|
|
|
31
31
|
test('tools at text verbosity: text, edits and errors only', () => {
|
|
32
32
|
const { effects } = replay({
|
|
33
33
|
events: loadFixture('tools.events.jsonl'),
|
|
34
|
-
prefs: { verbosity: 'text',
|
|
34
|
+
prefs: { verbosity: 'text', models: {} },
|
|
35
35
|
})
|
|
36
36
|
expect(effectLines(effects)).toMatchInlineSnapshot(`
|
|
37
37
|
[
|
|
38
|
-
"» **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.",
|
|
38
|
+
"-# » **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.",
|
|
39
39
|
"[typing on]",
|
|
40
40
|
"-# *using openai/gpt-6-luna low ⋅ build*",
|
|
41
41
|
"-# ⨯ shell _Invalid arguments for tool "shell":_",
|
|
42
42
|
"-# ⨯ shell _Invalid arguments for tool "shell":_",
|
|
43
43
|
"\\nDone. \`tmp-events/hello.txt\` contains \`hello world\`.",
|
|
44
44
|
"[typing off]",
|
|
45
|
-
"-# *
|
|
45
|
+
"-# *main ⋅ 17s ⋅ gpt-6-luna*",
|
|
46
46
|
]
|
|
47
47
|
`)
|
|
48
48
|
})
|
|
@@ -51,13 +51,13 @@ test('task-subagent: child tool lines labelled with the agent, no child text, fo
|
|
|
51
51
|
const { effects, view } = replay({ events: loadFixture('task-subagent.events.jsonl') })
|
|
52
52
|
expect(effectLines(effects)).toMatchInlineSnapshot(`
|
|
53
53
|
[
|
|
54
|
-
"» **user:** Use the subagent tool with agent "general" to find which file defines the Drizzle SQLite schema in cli/src (the subagent should use glob and read). Then read the first 10 lines of that file yourself with the read tool and answer in 2 short sentences.",
|
|
54
|
+
"-# » **user:** Use the subagent tool with agent "general" to find which file defines the Drizzle SQLite schema in cli/src (the subagent should use glob and read). Then read the first 10 lines of that file yourself with the read tool and answer in 2 short sentences.",
|
|
55
55
|
"[typing on]",
|
|
56
56
|
"-# *using openai/gpt-6-luna low ⋅ build*",
|
|
57
57
|
"-# ┣ general **Find SQLite schema file**",
|
|
58
58
|
"\\nThe Drizzle SQLite schema is defined in \`cli/src/schema.ts\`. Its first lines confirm it defines tables for Kimaki’s local SQLite database.",
|
|
59
59
|
"[typing off]",
|
|
60
|
-
"-# *
|
|
60
|
+
"-# *main ⋅ 12s ⋅ 6% ⋅ gpt-6-luna*",
|
|
61
61
|
]
|
|
62
62
|
`)
|
|
63
63
|
expect(view.children).toMatchInlineSnapshot(`
|
|
@@ -76,12 +76,12 @@ test('task-parallel: background children show start and end lines only, no foote
|
|
|
76
76
|
const { effects, view } = replay({ events: loadFixture('task-parallel.events.jsonl') })
|
|
77
77
|
expect(effectLines(effects)).toMatchInlineSnapshot(`
|
|
78
78
|
[
|
|
79
|
-
"» **user:** In a single step, call the subagent tool twice in parallel, both with agent "general": one to count the .ts files in cli/src/commands using a shell command, one to list the top-level folders of the repo with a shell command. Then reply with both results in 2 lines.",
|
|
79
|
+
"-# » **user:** In a single step, call the subagent tool twice in parallel, both with agent "general": one to count the .ts files in cli/src/commands using a shell command, one to list the top-level folders of the repo with a shell command. Then reply with both results in 2 lines.",
|
|
80
80
|
"[typing on]",
|
|
81
81
|
"-# *using openai/gpt-6-luna low ⋅ build*",
|
|
82
82
|
"-# ┣ general **Count command TypeScript** (background)",
|
|
83
83
|
"-# ┣ general **List repo folders** (background)",
|
|
84
|
-
"-#
|
|
84
|
+
"-# general finished: Count command TypeScript",
|
|
85
85
|
"\\nBoth tasks are running. I’ll report the two results when they finish.",
|
|
86
86
|
"\\n-# ┣ general **List repo folders**",
|
|
87
87
|
"-# ┣ general ⋅ shell _find . -maxdepth 1 -type d -not -name . -print \\| sed 's#^./##' \\| sort_",
|
|
@@ -115,16 +115,16 @@ test('abort: no error line for the aborted tool, no footer, next turn is normal'
|
|
|
115
115
|
const { effects } = replay({ events: loadFixture('abort.events.jsonl') })
|
|
116
116
|
expect(effectLines(effects)).toMatchInlineSnapshot(`
|
|
117
117
|
[
|
|
118
|
-
"» **user:** Run \`sleep 30 && echo never\` with bash, then summarize.",
|
|
118
|
+
"-# » **user:** Run \`sleep 30 && echo never\` with bash, then summarize.",
|
|
119
119
|
"[typing on]",
|
|
120
120
|
"-# *using openai/gpt-6-luna low ⋅ build*",
|
|
121
121
|
"-# ┣ shell _sleep 30 && echo never_",
|
|
122
122
|
"[typing off]",
|
|
123
|
-
"» **user:** Say only: resumed after abort.",
|
|
123
|
+
"-# » **user:** Say only: resumed after abort.",
|
|
124
124
|
"[typing on]",
|
|
125
125
|
"resumed after abort",
|
|
126
126
|
"[typing off]",
|
|
127
|
-
"-# *
|
|
127
|
+
"-# *main ⋅ 1s ⋅ 6% ⋅ gpt-6-luna*",
|
|
128
128
|
]
|
|
129
129
|
`)
|
|
130
130
|
})
|
|
@@ -185,23 +185,25 @@ test('queue-plain: acks with positions while busy, silent footer before each del
|
|
|
185
185
|
const { effects, view } = replay({ events: loadFixture('queue-plain.events.jsonl') })
|
|
186
186
|
expect(effectLines(effects)).toMatchInlineSnapshot(`
|
|
187
187
|
[
|
|
188
|
-
"» **user:** Run \`sleep 6 && echo slow-done\` with bash, then say done.",
|
|
188
|
+
"-# » **user:** Run \`sleep 6 && echo slow-done\` with bash, then say done.",
|
|
189
189
|
"[typing on]",
|
|
190
190
|
"-# *using openai/gpt-6-luna low ⋅ build*",
|
|
191
191
|
"-# ┣ shell _sleep 6 && echo slow-done_",
|
|
192
|
-
"[show queue:msg_0f20347e1001nsJ7W4hOvhBpQb] -#
|
|
193
|
-
|
|
192
|
+
"[show queue:msg_0f20347e1001nsJ7W4hOvhBpQb] -# » **user:** QUEUED-1: reply with the word apple.
|
|
193
|
+
-# Queued at position 1. Remove it with /clear-queue position:1",
|
|
194
|
+
"[show queue:msg_0f20347e500196F4o0voD7LGMV] -# » **user:** QUEUED-2: reply with the word cherry.
|
|
195
|
+
-# Queued at position 2. Remove it with /clear-queue position:2",
|
|
194
196
|
"\\nDone.",
|
|
195
|
-
"[silent] -# *
|
|
196
|
-
"[edit queue:msg_0f20347e1001nsJ7W4hOvhBpQb] -#
|
|
197
|
-
"
|
|
197
|
+
"[silent] -# *main ⋅ 10s ⋅ 6% ⋅ gpt-6-luna*",
|
|
198
|
+
"[edit queue:msg_0f20347e1001nsJ7W4hOvhBpQb] -# » **user:** QUEUED-1: reply with the word apple.",
|
|
199
|
+
"[reply to queue:msg_0f20347e1001nsJ7W4hOvhBpQb] -# Queued message sent",
|
|
198
200
|
"apple",
|
|
199
|
-
"[silent] -# *
|
|
200
|
-
"[edit queue:msg_0f20347e500196F4o0voD7LGMV] -#
|
|
201
|
-
"
|
|
201
|
+
"[silent] -# *main ⋅ 2s ⋅ 6% ⋅ gpt-6-luna*",
|
|
202
|
+
"[edit queue:msg_0f20347e500196F4o0voD7LGMV] -# » **user:** QUEUED-2: reply with the word cherry.",
|
|
203
|
+
"[reply to queue:msg_0f20347e500196F4o0voD7LGMV] -# Queued message sent",
|
|
202
204
|
"cherry",
|
|
203
205
|
"[typing off]",
|
|
204
|
-
"-# *
|
|
206
|
+
"-# *main ⋅ 2s ⋅ 6% ⋅ gpt-6-luna*",
|
|
205
207
|
]
|
|
206
208
|
`)
|
|
207
209
|
expect(view.inbox).toMatchInlineSnapshot(`[]`)
|
|
@@ -211,21 +213,25 @@ test('steer-queue: cancelled item edits its ack, interrupted run has no footer',
|
|
|
211
213
|
const { effects, view } = replay({ events: loadFixture('steer-queue.events.jsonl') })
|
|
212
214
|
expect(effectLines(effects)).toMatchInlineSnapshot(`
|
|
213
215
|
[
|
|
214
|
-
"» **user:** Run \`sleep 8 && echo slow-done\` with bash, then say done.",
|
|
216
|
+
"-# » **user:** Run \`sleep 8 && echo slow-done\` with bash, then say done.",
|
|
215
217
|
"[typing on]",
|
|
216
218
|
"-# *using openai/gpt-6-luna low ⋅ build*",
|
|
217
219
|
"-# ┣ shell _sleep 8 && echo slow-done_",
|
|
218
|
-
"[show queue:msg_0f200f884001tIpWlHhNB8Y5bv] -#
|
|
219
|
-
|
|
220
|
-
"[show queue:
|
|
221
|
-
|
|
222
|
-
"» **user:**
|
|
220
|
+
"[show queue:msg_0f200f884001tIpWlHhNB8Y5bv] -# » **user:** QUEUED-A: after everything, reply with the word apple.
|
|
221
|
+
-# Queued at position 1. Remove it with /clear-queue position:1",
|
|
222
|
+
"[show queue:msg_kimaki_queued_b_test1] -# » **user:** QUEUED-B: reply with the word banana.
|
|
223
|
+
-# Queued at position 2. Remove it with /clear-queue position:2",
|
|
224
|
+
"[show queue:msg_0f200f88a001SXny4FyIMuMPS6] -# » **user:** QUEUED-C: reply with the word cherry.
|
|
225
|
+
-# Queued at position 3. Remove it with /clear-queue position:3",
|
|
226
|
+
"[edit queue:msg_kimaki_queued_b_test1] -# » **user:** QUEUED-B: reply with the word banana.
|
|
227
|
+
-# Removed from queue",
|
|
228
|
+
"-# » **user:** STEER: actually also tell me the current date using bash \`date\`.",
|
|
223
229
|
"[typing off]",
|
|
224
230
|
"[typing on]",
|
|
225
231
|
"-# ┣ shell _date_",
|
|
226
232
|
"\\nDone. The current date is Wed Sep 30 2026.",
|
|
227
233
|
"[typing off]",
|
|
228
|
-
"-# *
|
|
234
|
+
"-# *main ⋅ 3s ⋅ 6% ⋅ gpt-6-luna*",
|
|
229
235
|
]
|
|
230
236
|
`)
|
|
231
237
|
// The recorded order (prompt, then interrupt with resume) parks queued items.
|
|
@@ -241,21 +247,22 @@ test('queue-parked: parked item runs and echoes after the next prompt, with a fo
|
|
|
241
247
|
const { effects } = replay({ events: loadFixture('queue-parked.events.jsonl') })
|
|
242
248
|
expect(effectLines(effects)).toMatchInlineSnapshot(`
|
|
243
249
|
[
|
|
244
|
-
"» **user:** Run \`sleep 6 && echo slow-done\` with bash, then say done.",
|
|
250
|
+
"-# » **user:** Run \`sleep 6 && echo slow-done\` with bash, then say done.",
|
|
245
251
|
"[typing on]",
|
|
246
252
|
"-# *using openai/gpt-6-luna low ⋅ build*",
|
|
247
253
|
"-# ┣ shell _sleep 6 && echo slow-done_",
|
|
248
|
-
"[show queue:msg_0f2039909001oR0GJKPGqxOjml] -#
|
|
254
|
+
"[show queue:msg_0f2039909001oR0GJKPGqxOjml] -# » **user:** PARKED-1: reply with the word apple.
|
|
255
|
+
-# Queued at position 1. Remove it with /clear-queue position:1",
|
|
249
256
|
"[typing off]",
|
|
250
|
-
"» **user:** NEW: reply with the word kiwi.",
|
|
257
|
+
"-# » **user:** NEW: reply with the word kiwi.",
|
|
251
258
|
"[typing on]",
|
|
252
259
|
"kiwi",
|
|
253
|
-
"[silent] -# *
|
|
254
|
-
"[edit queue:msg_0f2039909001oR0GJKPGqxOjml] -#
|
|
255
|
-
"
|
|
260
|
+
"[silent] -# *main ⋅ 3s ⋅ 6% ⋅ gpt-6-luna*",
|
|
261
|
+
"[edit queue:msg_0f2039909001oR0GJKPGqxOjml] -# » **user:** PARKED-1: reply with the word apple.",
|
|
262
|
+
"[reply to queue:msg_0f2039909001oR0GJKPGqxOjml] -# Queued message sent",
|
|
256
263
|
"apple",
|
|
257
264
|
"[typing off]",
|
|
258
|
-
"-# *
|
|
265
|
+
"-# *main ⋅ 3s ⋅ 6% ⋅ gpt-6-luna*",
|
|
259
266
|
]
|
|
260
267
|
`)
|
|
261
268
|
})
|
|
@@ -264,7 +271,7 @@ test('question: one dropdown per question, typing off while waiting, edited on r
|
|
|
264
271
|
const { effects, view } = replay({ events: loadFixture('question.events.jsonl') })
|
|
265
272
|
expect(effectLines(effects)).toMatchInlineSnapshot(`
|
|
266
273
|
[
|
|
267
|
-
"» **user:** Use the question tool to ask me which color I prefer, with options red, green, blue (single choice), and in the same question tool call add a second question asking which fruits I like with options apple, pear, kiwi (multiple choice). After my answer, reply with one line.",
|
|
274
|
+
"-# » **user:** Use the question tool to ask me which color I prefer, with options red, green, blue (single choice), and in the same question tool call add a second question asking which fruits I like with options apple, pear, kiwi (multiple choice). After my answer, reply with one line.",
|
|
268
275
|
"[typing on]",
|
|
269
276
|
"-# *using openai/gpt-6-luna low ⋅ build*",
|
|
270
277
|
"[typing off]",
|
|
@@ -279,7 +286,7 @@ test('question: one dropdown per question, typing off while waiting, edited on r
|
|
|
279
286
|
✓ _apple, kiwi_",
|
|
280
287
|
"You prefer green, and you like apples and kiwi.",
|
|
281
288
|
"[typing off]",
|
|
282
|
-
"-# *
|
|
289
|
+
"-# *main ⋅ 5s ⋅ 6% ⋅ gpt-6-luna*",
|
|
283
290
|
]
|
|
284
291
|
`)
|
|
285
292
|
expect(view.forms).toMatchInlineSnapshot(`{}`)
|
|
@@ -289,7 +296,7 @@ test('permission: two requests, the rejected one fails its tool call', () => {
|
|
|
289
296
|
const { effects, view } = replay({ events: loadFixture('permission.events.jsonl') })
|
|
290
297
|
expect(effectLines(effects)).toMatchInlineSnapshot(`
|
|
291
298
|
[
|
|
292
|
-
"» **user:** Run \`echo first\` with bash. Then run \`echo second\` with bash. Then reply with one line describing what happened.",
|
|
299
|
+
"-# » **user:** Run \`echo first\` with bash. Then run \`echo second\` with bash. Then reply with one line describing what happened.",
|
|
293
300
|
"[typing on]",
|
|
294
301
|
"-# *using openai/gpt-6-luna low ⋅ build*",
|
|
295
302
|
"-# ┣ shell _echo first_",
|
|
@@ -315,7 +322,7 @@ test('permission: two requests, the rejected one fails its tool call', () => {
|
|
|
315
322
|
"-# ⨯ shell _Unable to execute command: echo second_",
|
|
316
323
|
"\\n\`echo first\` printed \`first\`; \`echo second\` failed to execute.",
|
|
317
324
|
"[typing off]",
|
|
318
|
-
"-# *
|
|
325
|
+
"-# *main ⋅ 5s ⋅ 6% ⋅ gpt-6-luna*",
|
|
319
326
|
]
|
|
320
327
|
`)
|
|
321
328
|
expect(view.permissions).toMatchInlineSnapshot(`{}`)
|
|
@@ -323,26 +330,26 @@ test('permission: two requests, the rejected one fails its tool call', () => {
|
|
|
323
330
|
|
|
324
331
|
test('shell-background: background shell line even without side effects, finished line when its job ends', () => {
|
|
325
332
|
const events = loadFixture('shell-background.events.jsonl')
|
|
326
|
-
const view = emptyView({ sessionId: 'ses_ee56d6caeffech0rMI5NTnkky1', channelId: 'channel', directory: '/project', isNew: false })
|
|
327
|
-
const { effects, view: end } = replay({ events, view, prefs: { verbosity: 'tools',
|
|
333
|
+
const view = emptyView({ sessionId: 'ses_ee56d6caeffech0rMI5NTnkky1', channelId: 'channel', directory: '/project', isNew: false, coordinator: false })
|
|
334
|
+
const { effects, view: end } = replay({ events, view, prefs: { verbosity: 'tools', models: {} } })
|
|
328
335
|
expect(effectLines(effects)).toMatchInlineSnapshot(`
|
|
329
336
|
[
|
|
330
|
-
"» **user:** start a task in background that runs sleep 100",
|
|
337
|
+
"-# » **user:** start a task in background that runs sleep 100",
|
|
331
338
|
"[typing on]",
|
|
332
339
|
"-# ┣ shell _Run sleep 100 in background_ (background)",
|
|
333
340
|
"\\nI started \`sleep 100\` in the background (shell ID \`sh_11a929cf5001mAukdL6rQFIbLg\`). It ends in about 100 seconds, and I get a notification when it does.",
|
|
334
341
|
"[typing off]",
|
|
335
|
-
"-# *
|
|
336
|
-
"» **user:** is it still running",
|
|
342
|
+
"-# *main ⋅ 4s ⋅ claude-opus-5-5 ⋅ opus*",
|
|
343
|
+
"-# » **user:** is it still running",
|
|
337
344
|
"[typing on]",
|
|
338
345
|
"Yes, it is still running (PID 47861). I have not had the completion notification yet.",
|
|
339
346
|
"[typing off]",
|
|
340
|
-
"-# *
|
|
341
|
-
"-#
|
|
347
|
+
"-# *main ⋅ 5s ⋅ claude-opus-5-5 ⋅ opus*",
|
|
348
|
+
"-# background shell finished: _Run sleep 100 in background_",
|
|
342
349
|
"[typing on]",
|
|
343
350
|
"\\nThe background \`sleep 100\` has finished. It printed no output.",
|
|
344
351
|
"[typing off]",
|
|
345
|
-
"-# *
|
|
352
|
+
"-# *main ⋅ 2s ⋅ claude-opus-5-5 ⋅ opus*",
|
|
346
353
|
]
|
|
347
354
|
`)
|
|
348
355
|
expect(end.shells).toMatchInlineSnapshot(`{}`)
|
|
@@ -357,8 +364,8 @@ test('shell: user !cmd output while idle and while busy, synthetic items render
|
|
|
357
364
|
user-shell-idle
|
|
358
365
|
05de62d feat: make external opencode session sync opt-in via --enable-sync
|
|
359
366
|
\`\`\`",
|
|
360
|
-
"» **user:** CONTEXT ONLY: the deploy tag is v9.9.9",
|
|
361
|
-
"» **user:** What did my shell command print, and what is the deploy tag? Also run \`sleep 5\` with bash first.",
|
|
367
|
+
"-# » **user:** CONTEXT ONLY: the deploy tag is v9.9.9",
|
|
368
|
+
"-# » **user:** What did my shell command print, and what is the deploy tag? Also run \`sleep 5\` with bash first.",
|
|
362
369
|
"[typing on]",
|
|
363
370
|
"-# *using openai/gpt-6-luna low ⋅ build*",
|
|
364
371
|
"-# ┣ shell _sleep 5_",
|
|
@@ -368,7 +375,7 @@ test('shell: user !cmd output while idle and while busy, synthetic items render
|
|
|
368
375
|
\`\`\`",
|
|
369
376
|
"\\nYour shell command printed \`user-shell-idle\` and \`05de62d feat: make external opencode session sync opt-in via --enable-sync\`. The deploy tag is \`v9.9.9\`.",
|
|
370
377
|
"[typing off]",
|
|
371
|
-
"-# *
|
|
378
|
+
"-# *main ⋅ 9s ⋅ 6% ⋅ gpt-6-luna*",
|
|
372
379
|
]
|
|
373
380
|
`)
|
|
374
381
|
})
|