kimaki 0.7.0 → 0.8.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/agent-model.e2e.test.js +7 -0
- package/dist/anthropic-auth-plugin.js +52 -18
- package/dist/cli-send-thread.e2e.test.js +4 -2
- package/dist/cli.js +11 -5
- package/dist/commands/add-dir.js +57 -10
- package/dist/commands/last-sessions.js +120 -0
- package/dist/commands/permissions.js +46 -7
- package/dist/discord-command-registration.js +5 -0
- package/dist/format-tables.test.js +19 -0
- package/dist/gateway-proxy.e2e.test.js +4 -1
- package/dist/interaction-handler.js +7 -0
- package/dist/logger.js +19 -20
- package/dist/message-formatting.js +3 -1
- package/dist/message-formatting.test.js +43 -1
- package/dist/opencode.js +1 -0
- package/dist/proxy-ws-preload.cjs +85 -0
- package/dist/queue-advanced-abort.e2e.test.js +2 -1
- package/dist/queue-advanced-action-buttons.e2e.test.js +2 -0
- package/dist/queue-advanced-footer.e2e.test.js +7 -0
- package/dist/queue-advanced-model-switch.e2e.test.js +1 -0
- package/dist/queue-advanced-permissions-typing.e2e.test.js +1 -0
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +1 -0
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +1 -0
- package/dist/queue-interrupt-drain.e2e.test.js +1 -0
- package/dist/queue-question-select-drain.e2e.test.js +2 -0
- package/dist/runtime-lifecycle.e2e.test.js +6 -4
- package/dist/session-handler/thread-session-runtime.js +32 -2
- package/dist/system-message.js +43 -29
- package/dist/system-message.test.js +47 -29
- package/dist/thread-message-queue.e2e.test.js +8 -1
- package/dist/undo-redo.e2e.test.js +1 -0
- package/dist/voice-message.e2e.test.js +8 -0
- package/package.json +9 -10
- package/skills/new-skill/SKILL.md +34 -20
- package/skills/readme.md +20 -0
- package/src/agent-model.e2e.test.ts +7 -0
- package/src/anthropic-auth-plugin.ts +64 -20
- package/src/cli-send-thread.e2e.test.ts +4 -2
- package/src/cli.ts +13 -5
- package/src/commands/add-dir.ts +85 -14
- package/src/commands/last-sessions.ts +167 -0
- package/src/commands/permissions.ts +62 -13
- package/src/discord-command-registration.ts +5 -0
- package/src/format-tables.test.ts +20 -0
- package/src/gateway-proxy.e2e.test.ts +4 -1
- package/src/interaction-handler.ts +8 -0
- package/src/logger.ts +46 -35
- package/src/message-formatting.test.ts +46 -1
- package/src/message-formatting.ts +3 -1
- package/src/opencode.ts +1 -0
- package/src/queue-advanced-abort.e2e.test.ts +2 -1
- package/src/queue-advanced-action-buttons.e2e.test.ts +2 -0
- package/src/queue-advanced-footer.e2e.test.ts +7 -0
- package/src/queue-advanced-model-switch.e2e.test.ts +1 -0
- package/src/queue-advanced-permissions-typing.e2e.test.ts +1 -0
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +1 -0
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +1 -0
- package/src/queue-interrupt-drain.e2e.test.ts +1 -0
- package/src/queue-question-select-drain.e2e.test.ts +2 -0
- package/src/runtime-lifecycle.e2e.test.ts +6 -4
- package/src/session-handler/thread-session-runtime.ts +48 -2
- package/src/system-message.test.ts +47 -29
- package/src/system-message.ts +43 -29
- package/src/thread-message-queue.e2e.test.ts +8 -1
- package/src/undo-redo.e2e.test.ts +1 -0
- package/src/voice-message.e2e.test.ts +8 -0
package/dist/logger.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// Prefixed logging utility using @clack/prompts for consistent
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Prefixed logging utility using @clack/prompts for consistent stderr diagnostics and file logs.
|
|
2
|
+
// Never write logger output to stdout because many CLI subcommands print
|
|
3
|
+
// machine-readable data there, for example `kimaki project list --json`.
|
|
4
4
|
import { log as clackLog } from '@clack/prompts';
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
@@ -106,7 +106,7 @@ export function formatErrorWithStack(error) {
|
|
|
106
106
|
redactPaths: false,
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
|
-
function writeToFile(level, prefix, args) {
|
|
109
|
+
function writeToFile({ level, prefix, args, }) {
|
|
110
110
|
const timestamp = new Date().toISOString();
|
|
111
111
|
const message = `[${timestamp}] [${level}] [${prefix}] ${args.map(formatArg).join(' ')}\n`;
|
|
112
112
|
if (!logFilePath) {
|
|
@@ -118,13 +118,10 @@ function getTimestamp() {
|
|
|
118
118
|
const now = new Date();
|
|
119
119
|
return `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
|
|
120
120
|
}
|
|
121
|
-
function
|
|
122
|
-
return prefix.padEnd(MAX_PREFIX_LENGTH);
|
|
123
|
-
}
|
|
124
|
-
function formatMessage(timestamp, prefix, args) {
|
|
121
|
+
function formatMessage({ timestamp, prefix, args, }) {
|
|
125
122
|
return [pc.dim(timestamp), prefix, ...args.map(formatArg)].join(' ');
|
|
126
123
|
}
|
|
127
|
-
const
|
|
124
|
+
const stderrLogOptions = { output: process.stderr, spacing: 0 };
|
|
128
125
|
// Suppress clack terminal output during vitest runs to avoid flooding
|
|
129
126
|
// test output with hundreds of log lines. File logging still works.
|
|
130
127
|
// Set KIMAKI_TEST_LOGS=1 when rerunning a failing test to see all
|
|
@@ -132,39 +129,41 @@ const noSpacing = { spacing: 0 };
|
|
|
132
129
|
const isVitest = !!process.env['KIMAKI_VITEST'];
|
|
133
130
|
const showTestLogs = isVitest && !!process.env['KIMAKI_TEST_LOGS'];
|
|
134
131
|
export function createLogger(prefix) {
|
|
135
|
-
const paddedPrefix =
|
|
132
|
+
const paddedPrefix = prefix.padEnd(MAX_PREFIX_LENGTH);
|
|
136
133
|
const suppressConsole = isVitest && !showTestLogs;
|
|
137
134
|
const log = (...args) => {
|
|
138
|
-
writeToFile('LOG', prefix, args);
|
|
135
|
+
writeToFile({ level: 'LOG', prefix, args });
|
|
139
136
|
if (suppressConsole) {
|
|
140
137
|
return;
|
|
141
138
|
}
|
|
142
|
-
clackLog.message(formatMessage(getTimestamp(), pc.cyan(paddedPrefix), args),
|
|
143
|
-
...noSpacing,
|
|
144
|
-
});
|
|
139
|
+
clackLog.message(formatMessage({ timestamp: getTimestamp(), prefix: pc.cyan(paddedPrefix), args }), stderrLogOptions);
|
|
145
140
|
};
|
|
146
141
|
return {
|
|
147
142
|
log,
|
|
148
143
|
error: (...args) => {
|
|
149
|
-
writeToFile('ERROR', prefix, args);
|
|
144
|
+
writeToFile({ level: 'ERROR', prefix, args });
|
|
150
145
|
if (suppressConsole) {
|
|
151
146
|
return;
|
|
152
147
|
}
|
|
153
|
-
clackLog.error(formatMessage(getTimestamp(), pc.red(paddedPrefix), args),
|
|
148
|
+
clackLog.error(formatMessage({ timestamp: getTimestamp(), prefix: pc.red(paddedPrefix), args }), stderrLogOptions);
|
|
154
149
|
},
|
|
155
150
|
warn: (...args) => {
|
|
156
|
-
writeToFile('WARN', prefix, args);
|
|
151
|
+
writeToFile({ level: 'WARN', prefix, args });
|
|
157
152
|
if (suppressConsole) {
|
|
158
153
|
return;
|
|
159
154
|
}
|
|
160
|
-
clackLog.warn(formatMessage(
|
|
155
|
+
clackLog.warn(formatMessage({
|
|
156
|
+
timestamp: getTimestamp(),
|
|
157
|
+
prefix: pc.yellow(paddedPrefix),
|
|
158
|
+
args,
|
|
159
|
+
}), stderrLogOptions);
|
|
161
160
|
},
|
|
162
161
|
info: (...args) => {
|
|
163
|
-
writeToFile('INFO', prefix, args);
|
|
162
|
+
writeToFile({ level: 'INFO', prefix, args });
|
|
164
163
|
if (suppressConsole) {
|
|
165
164
|
return;
|
|
166
165
|
}
|
|
167
|
-
clackLog.info(formatMessage(getTimestamp(), pc.blue(paddedPrefix), args),
|
|
166
|
+
clackLog.info(formatMessage({ timestamp: getTimestamp(), prefix: pc.blue(paddedPrefix), args }), stderrLogOptions);
|
|
168
167
|
},
|
|
169
168
|
debug: log,
|
|
170
169
|
};
|
|
@@ -307,7 +307,9 @@ export function formatPart(part, prefix) {
|
|
|
307
307
|
}
|
|
308
308
|
const firstChar = text[0] || '';
|
|
309
309
|
const markdownStarters = ['#', '*', '_', '-', '>', '`', '[', '|'];
|
|
310
|
-
const startsWithMarkdown = markdownStarters.includes(firstChar) ||
|
|
310
|
+
const startsWithMarkdown = markdownStarters.includes(firstChar) ||
|
|
311
|
+
/^\d+\./.test(text) ||
|
|
312
|
+
/^<callout[\s>]/i.test(text);
|
|
311
313
|
if (startsWithMarkdown) {
|
|
312
314
|
return `\n${text}`;
|
|
313
315
|
}
|
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
import { describe, test, expect } from 'vitest';
|
|
2
|
-
import { formatTodoList } from './message-formatting.js';
|
|
2
|
+
import { formatPart, formatTodoList } from './message-formatting.js';
|
|
3
|
+
describe('formatPart', () => {
|
|
4
|
+
test('callout text does not get ⬥ prefix', () => {
|
|
5
|
+
const part = {
|
|
6
|
+
id: 'test',
|
|
7
|
+
type: 'text',
|
|
8
|
+
sessionID: 'ses_test',
|
|
9
|
+
messageID: 'msg_test',
|
|
10
|
+
text: `<callout accent="#ef4444">\n## Top priority\n- **Stripe dispute** deadline\n</callout>`,
|
|
11
|
+
};
|
|
12
|
+
expect(formatPart(part)).toMatchInlineSnapshot(`
|
|
13
|
+
"
|
|
14
|
+
<callout accent="#ef4444">
|
|
15
|
+
## Top priority
|
|
16
|
+
- **Stripe dispute** deadline
|
|
17
|
+
</callout>"
|
|
18
|
+
`);
|
|
19
|
+
});
|
|
20
|
+
test('regular text gets ⬥ prefix', () => {
|
|
21
|
+
const part = {
|
|
22
|
+
id: 'test',
|
|
23
|
+
type: 'text',
|
|
24
|
+
sessionID: 'ses_test',
|
|
25
|
+
messageID: 'msg_test',
|
|
26
|
+
text: 'hello world',
|
|
27
|
+
};
|
|
28
|
+
expect(formatPart(part)).toMatchInlineSnapshot(`"⬥ hello world"`);
|
|
29
|
+
});
|
|
30
|
+
test('text starting with heading does not get ⬥ prefix', () => {
|
|
31
|
+
const part = {
|
|
32
|
+
id: 'test',
|
|
33
|
+
type: 'text',
|
|
34
|
+
sessionID: 'ses_test',
|
|
35
|
+
messageID: 'msg_test',
|
|
36
|
+
text: '## Summary\nDone.',
|
|
37
|
+
};
|
|
38
|
+
expect(formatPart(part)).toMatchInlineSnapshot(`
|
|
39
|
+
"
|
|
40
|
+
## Summary
|
|
41
|
+
Done."
|
|
42
|
+
`);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
3
45
|
describe('formatTodoList', () => {
|
|
4
46
|
test('formats active todo with monospace numbers', () => {
|
|
5
47
|
const part = {
|
package/dist/opencode.js
CHANGED
|
@@ -521,6 +521,7 @@ async function startSingleServer({ directory, } = {}) {
|
|
|
521
521
|
KIMAKI: '1',
|
|
522
522
|
KIMAKI_DATA_DIR: getDataDir(),
|
|
523
523
|
KIMAKI_LOCK_PORT: getLockPort().toString(),
|
|
524
|
+
KIMAKI_PARENT_LOCK_PORT: getLockPort().toString(),
|
|
524
525
|
...(gatewayToken && { KIMAKI_DB_AUTH_TOKEN: gatewayToken }),
|
|
525
526
|
// Guard: prevents agents from running `kimaki` root command inside
|
|
526
527
|
// an OpenCode session, which would steal the lock port and break the bot.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// WebSocket proxy preload for environments behind a firewall or VPN (e.g. GFW).
|
|
2
|
+
// Loaded via --require when HTTPS_PROXY / https_proxy / HTTP_PROXY / http_proxy is set.
|
|
3
|
+
//
|
|
4
|
+
// Monkey-patches Module.prototype.require to intercept `ws` module loads and
|
|
5
|
+
// inject an https-proxy-agent into every client-side WebSocket connection.
|
|
6
|
+
// This is needed because:
|
|
7
|
+
// 1. Node.js --use-env-proxy breaks ws WebSocket upgrades (nodejs/node#62054)
|
|
8
|
+
// 2. discord.js doesn't expose an agent option for WebSocket connections
|
|
9
|
+
// 3. @discordjs/ws imports ws via ESM, so require.cache patches don't work
|
|
10
|
+
// 4. The ws library supports agent in options but discord.js doesn't pass it
|
|
11
|
+
//
|
|
12
|
+
// Tracking issues:
|
|
13
|
+
// - nodejs/node#57872 (proxy env var support in Node.js)
|
|
14
|
+
// - nodejs/node#62054 (--use-env-proxy breaks ws)
|
|
15
|
+
// - discordjs/discord.js#10716 (proxy support for WebSocket, closed won't fix)
|
|
16
|
+
'use strict';
|
|
17
|
+
const proxyUrl = process.env.https_proxy ||
|
|
18
|
+
process.env.HTTPS_PROXY ||
|
|
19
|
+
process.env.http_proxy ||
|
|
20
|
+
process.env.HTTP_PROXY;
|
|
21
|
+
if (!proxyUrl) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
let HttpsProxyAgent;
|
|
25
|
+
try {
|
|
26
|
+
HttpsProxyAgent = require('https-proxy-agent').HttpsProxyAgent;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// https-proxy-agent not installed; skip patching
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const agent = new HttpsProxyAgent(proxyUrl);
|
|
33
|
+
const Module = require('module');
|
|
34
|
+
const origRequire = Module.prototype.require;
|
|
35
|
+
Module.prototype.require = function patchedRequire(id) {
|
|
36
|
+
const mod = origRequire.apply(this, arguments);
|
|
37
|
+
// Only intercept the ws module, and only once
|
|
38
|
+
if (id === 'ws' && mod && !mod.__kimakiProxyPatched) {
|
|
39
|
+
const OrigWS = mod.WebSocket || mod;
|
|
40
|
+
if (typeof OrigWS === 'function' && !OrigWS.__kimakiProxyPatched) {
|
|
41
|
+
const PatchedWS = function ProxiedWebSocket(url, protocols, options) {
|
|
42
|
+
// ws allows protocols to be an options object when omitted
|
|
43
|
+
if (typeof protocols === 'object' &&
|
|
44
|
+
protocols !== null &&
|
|
45
|
+
!Array.isArray(protocols)) {
|
|
46
|
+
options = protocols;
|
|
47
|
+
protocols = undefined;
|
|
48
|
+
}
|
|
49
|
+
// Inject agent for client connections only (url !== null).
|
|
50
|
+
// Don't override if caller already provided an agent.
|
|
51
|
+
if (url !== null && (!options || !options.agent)) {
|
|
52
|
+
options = Object.assign({}, options, { agent });
|
|
53
|
+
}
|
|
54
|
+
if (new.target) {
|
|
55
|
+
return Reflect.construct(OrigWS, [url, protocols, options], new.target);
|
|
56
|
+
}
|
|
57
|
+
return new OrigWS(url, protocols, options);
|
|
58
|
+
};
|
|
59
|
+
// Inherit prototype so instanceof checks work
|
|
60
|
+
Object.setPrototypeOf(PatchedWS.prototype, OrigWS.prototype);
|
|
61
|
+
Object.setPrototypeOf(PatchedWS, OrigWS);
|
|
62
|
+
PatchedWS.prototype.constructor = PatchedWS;
|
|
63
|
+
// Copy static constants (CONNECTING, OPEN, CLOSING, CLOSED) and statics
|
|
64
|
+
for (const key of Object.getOwnPropertyNames(OrigWS)) {
|
|
65
|
+
if (key !== 'length' && key !== 'prototype' && key !== 'name') {
|
|
66
|
+
try {
|
|
67
|
+
Object.defineProperty(PatchedWS, key, Object.getOwnPropertyDescriptor(OrigWS, key));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// some properties may not be configurable
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
PatchedWS.__kimakiProxyPatched = true;
|
|
75
|
+
mod.WebSocket = PatchedWS;
|
|
76
|
+
// ws/index.js sets module.exports = WebSocket, so patch the default too
|
|
77
|
+
if (typeof mod === 'function') {
|
|
78
|
+
// Can't replace module.exports from here, but the WebSocket property
|
|
79
|
+
// is what @discordjs/ws uses via `import { WebSocket } from "ws"`
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
mod.__kimakiProxyPatched = true;
|
|
83
|
+
}
|
|
84
|
+
return mod;
|
|
85
|
+
};
|
|
@@ -155,6 +155,7 @@ e2eTest('queue advanced: abort and retry', () => {
|
|
|
155
155
|
"--- from: user (queue-advanced-tester)
|
|
156
156
|
Reply with exactly: abort-no-footer-setup
|
|
157
157
|
--- from: assistant (TestBot)
|
|
158
|
+
*using deterministic-provider/deterministic-v2*
|
|
158
159
|
⬥ ok
|
|
159
160
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
160
161
|
--- from: user (queue-advanced-tester)
|
|
@@ -285,7 +286,7 @@ e2eTest('queue advanced: abort and retry', () => {
|
|
|
285
286
|
"--- from: user (queue-advanced-tester)
|
|
286
287
|
Reply with exactly: force-abort-setup
|
|
287
288
|
--- from: assistant (TestBot)
|
|
288
|
-
|
|
289
|
+
*using deterministic-provider/deterministic-v2*
|
|
289
290
|
--- from: user (queue-advanced-tester)
|
|
290
291
|
SLOW_ABORT_MARKER run long response"
|
|
291
292
|
`);
|
|
@@ -122,6 +122,7 @@ describe('queue advanced: action buttons', () => {
|
|
|
122
122
|
"--- from: user (queue-action-tester)
|
|
123
123
|
Reply with exactly: action-button-setup
|
|
124
124
|
--- from: assistant (TestBot)
|
|
125
|
+
*using deterministic-provider/deterministic-v2*
|
|
125
126
|
⬥ ok
|
|
126
127
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
127
128
|
**Action Required**
|
|
@@ -193,6 +194,7 @@ describe('queue advanced: action buttons', () => {
|
|
|
193
194
|
"--- from: user (queue-action-tester)
|
|
194
195
|
Reply with exactly: action-button-dismiss-setup
|
|
195
196
|
--- from: assistant (TestBot)
|
|
197
|
+
*using deterministic-provider/deterministic-v2*
|
|
196
198
|
⬥ ok
|
|
197
199
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
198
200
|
**Action Required**
|
|
@@ -33,6 +33,7 @@ e2eTest('queue advanced: footer emission', () => {
|
|
|
33
33
|
"--- from: user (queue-advanced-tester)
|
|
34
34
|
Reply with exactly: footer-check
|
|
35
35
|
--- from: assistant (TestBot)
|
|
36
|
+
*using deterministic-provider/deterministic-v2*
|
|
36
37
|
⬥ ok
|
|
37
38
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
38
39
|
`);
|
|
@@ -89,6 +90,7 @@ e2eTest('queue advanced: footer emission', () => {
|
|
|
89
90
|
"--- from: user (queue-advanced-tester)
|
|
90
91
|
Reply with exactly: footer-multi-setup
|
|
91
92
|
--- from: assistant (TestBot)
|
|
93
|
+
*using deterministic-provider/deterministic-v2*
|
|
92
94
|
⬥ ok
|
|
93
95
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
94
96
|
--- from: user (queue-advanced-tester)
|
|
@@ -185,6 +187,7 @@ e2eTest('queue advanced: footer emission', () => {
|
|
|
185
187
|
"--- from: user (queue-advanced-tester)
|
|
186
188
|
Reply with exactly: interrupt-footer-setup
|
|
187
189
|
--- from: assistant (TestBot)
|
|
190
|
+
*using deterministic-provider/deterministic-v2*
|
|
188
191
|
⬥ ok
|
|
189
192
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
190
193
|
--- from: user (queue-advanced-tester)
|
|
@@ -265,6 +268,7 @@ e2eTest('queue advanced: footer emission', () => {
|
|
|
265
268
|
"--- from: user (queue-advanced-tester)
|
|
266
269
|
Reply with exactly: plugin-timeout-setup
|
|
267
270
|
--- from: assistant (TestBot)
|
|
271
|
+
*using deterministic-provider/deterministic-v2*
|
|
268
272
|
⬥ ok
|
|
269
273
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
270
274
|
--- from: user (queue-advanced-tester)
|
|
@@ -357,6 +361,7 @@ e2eTest('queue advanced: footer emission', () => {
|
|
|
357
361
|
"--- from: user (queue-advanced-tester)
|
|
358
362
|
TOOL_CALL_FOOTER_MARKER
|
|
359
363
|
--- from: assistant (TestBot)
|
|
364
|
+
*using deterministic-provider/deterministic-v2*
|
|
360
365
|
⬥ running tool
|
|
361
366
|
⬥ ok
|
|
362
367
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
@@ -411,6 +416,7 @@ e2eTest('queue advanced: footer emission', () => {
|
|
|
411
416
|
"--- from: user (queue-advanced-tester)
|
|
412
417
|
MULTI_TOOL_FOOTER_MARKER
|
|
413
418
|
--- from: assistant (TestBot)
|
|
419
|
+
*using deterministic-provider/deterministic-v2*
|
|
414
420
|
⬥ investigating the issue
|
|
415
421
|
⬥ all done, fixed 3 files
|
|
416
422
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
@@ -467,6 +473,7 @@ e2eTest('queue advanced: footer emission', () => {
|
|
|
467
473
|
"--- from: user (queue-advanced-tester)
|
|
468
474
|
MULTI_STEP_CHAIN_MARKER
|
|
469
475
|
--- from: assistant (TestBot)
|
|
476
|
+
*using deterministic-provider/deterministic-v2*
|
|
470
477
|
⬥ chain step 1: reading config
|
|
471
478
|
⬥ chain step 2: analyzing results
|
|
472
479
|
⬥ chain step 3: applying fix
|
|
@@ -250,6 +250,7 @@ describe('queue advanced: /model with interrupt recovery', () => {
|
|
|
250
250
|
"--- from: user (queue-model-switch-tester)
|
|
251
251
|
Reply with exactly: model-switcher-setup
|
|
252
252
|
--- from: assistant (TestBot)
|
|
253
|
+
*using deterministic-provider/deterministic-v2*
|
|
253
254
|
⬥ ok
|
|
254
255
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
255
256
|
Model set for this session:
|
|
@@ -85,6 +85,7 @@ describe('queue advanced: typing around permissions', () => {
|
|
|
85
85
|
"--- from: user (queue-permission-tester)
|
|
86
86
|
PERMISSION_TYPING_MARKER
|
|
87
87
|
--- from: assistant (TestBot)
|
|
88
|
+
*using deterministic-provider/deterministic-v2*
|
|
88
89
|
⬥ requesting external read permission
|
|
89
90
|
⚠️ **Permission Required**
|
|
90
91
|
**Type:** \`external_directory\`
|
|
@@ -82,6 +82,7 @@ e2eTest('queue advanced: typing interrupt', () => {
|
|
|
82
82
|
"--- from: user (queue-advanced-tester)
|
|
83
83
|
Reply with exactly: typing-stop-interrupt-setup
|
|
84
84
|
--- from: assistant (TestBot)
|
|
85
|
+
*using deterministic-provider/deterministic-v2*
|
|
85
86
|
⬥ ok
|
|
86
87
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
87
88
|
--- from: user (queue-advanced-tester)
|
|
@@ -107,6 +107,7 @@ describe('queue drain with pending interactive UI', () => {
|
|
|
107
107
|
"--- from: user (drain-ui-tester)
|
|
108
108
|
Reply with exactly: drain-button-setup
|
|
109
109
|
--- from: assistant (TestBot)
|
|
110
|
+
*using deterministic-provider/deterministic-v2*
|
|
110
111
|
⬥ ok
|
|
111
112
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
112
113
|
**Action Required**
|
|
@@ -98,6 +98,7 @@ e2eTest('queue + interrupt drain ordering', () => {
|
|
|
98
98
|
"--- from: user (interrupt-tester)
|
|
99
99
|
Reply with exactly: setup-interrupt-drain
|
|
100
100
|
--- from: assistant (TestBot)
|
|
101
|
+
*using deterministic-provider/deterministic-v2*
|
|
101
102
|
⬥ ok
|
|
102
103
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
103
104
|
--- from: user (interrupt-tester)
|
|
@@ -121,6 +121,7 @@ describe('queue drain after question select answer', () => {
|
|
|
121
121
|
"--- from: user (question-select-tester)
|
|
122
122
|
QUESTION_SELECT_QUEUE_MARKER
|
|
123
123
|
--- from: assistant (TestBot)
|
|
124
|
+
*using deterministic-provider/deterministic-v2*
|
|
124
125
|
**Select action**
|
|
125
126
|
How to proceed?
|
|
126
127
|
✓ _Alpha_
|
|
@@ -233,6 +234,7 @@ describe('queue drain after question select answer', () => {
|
|
|
233
234
|
"--- from: user (question-select-tester)
|
|
234
235
|
QUESTION_SELECT_QUEUE_MARKER second-test
|
|
235
236
|
--- from: assistant (TestBot)
|
|
237
|
+
*using deterministic-provider/deterministic-v2*
|
|
236
238
|
**Select action**
|
|
237
239
|
How to proceed?
|
|
238
240
|
✓ _Alpha_
|
|
@@ -277,6 +277,7 @@ describe('runtime lifecycle', () => {
|
|
|
277
277
|
"--- from: user (lifecycle-tester)
|
|
278
278
|
Reply with exactly: seq-alpha
|
|
279
279
|
--- from: assistant (TestBot)
|
|
280
|
+
*using deterministic-provider/deterministic-v2*
|
|
280
281
|
⬥ ok
|
|
281
282
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
282
283
|
--- from: user (lifecycle-tester)
|
|
@@ -307,7 +308,7 @@ describe('runtime lifecycle', () => {
|
|
|
307
308
|
discord,
|
|
308
309
|
threadId: thread.id,
|
|
309
310
|
userId: TEST_USER_ID,
|
|
310
|
-
text: '
|
|
311
|
+
text: '%',
|
|
311
312
|
timeout: 4_000,
|
|
312
313
|
});
|
|
313
314
|
const messages = await discord.thread(thread.id).getMessages();
|
|
@@ -318,12 +319,13 @@ describe('runtime lifecycle', () => {
|
|
|
318
319
|
if (!message.content.startsWith('*')) {
|
|
319
320
|
return false;
|
|
320
321
|
}
|
|
321
|
-
return message.content.includes('deterministic-v2');
|
|
322
|
+
return message.content.includes('deterministic-v2') && message.content.includes('%');
|
|
322
323
|
});
|
|
323
324
|
expect(await discord.thread(thread.id).text()).toMatchInlineSnapshot(`
|
|
324
325
|
"--- from: user (lifecycle-tester)
|
|
325
326
|
Reply with exactly: footer-check
|
|
326
327
|
--- from: assistant (TestBot)
|
|
328
|
+
*using deterministic-provider/deterministic-v2*
|
|
327
329
|
⬥ ok
|
|
328
330
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
329
331
|
`);
|
|
@@ -381,6 +383,7 @@ describe('runtime lifecycle', () => {
|
|
|
381
383
|
"--- from: user (lifecycle-tester)
|
|
382
384
|
Reply with exactly: reconnect-alpha
|
|
383
385
|
--- from: assistant (TestBot)
|
|
386
|
+
*using deterministic-provider/deterministic-v2*
|
|
384
387
|
⬥ ok
|
|
385
388
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
386
389
|
--- from: user (lifecycle-tester)
|
|
@@ -417,8 +420,7 @@ describe('runtime lifecycle', () => {
|
|
|
417
420
|
"--- from: user (lifecycle-tester)
|
|
418
421
|
Reply with exactly: footer-high-usage
|
|
419
422
|
--- from: assistant (TestBot)
|
|
420
|
-
|
|
421
|
-
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
423
|
+
*using deterministic-provider/deterministic-v2*"
|
|
422
424
|
`);
|
|
423
425
|
const threadText = await discord.thread(thread.id).text();
|
|
424
426
|
expect(threadText).not.toContain('⬦ context usage');
|
|
@@ -16,7 +16,7 @@ import { createLogger, LogPrefix } from '../logger.js';
|
|
|
16
16
|
import { sendThreadMessage, SILENT_MESSAGE_FLAGS, NOTIFY_MESSAGE_FLAGS, } from '../discord-utils.js';
|
|
17
17
|
import { formatPart } from '../message-formatting.js';
|
|
18
18
|
import { getChannelVerbosity, getPartMessageIds, setPartMessage, getThreadSession, setThreadSession, getThreadWorktree, setSessionAgent, getVariantCascade, setSessionStartSource, appendSessionEventsSinceLastTimestamp, getSessionEventSnapshot, } from '../database.js';
|
|
19
|
-
import { showPermissionButtons,
|
|
19
|
+
import { showPermissionButtons, addPermissionRequestToContext, arePatternsCoveredBy, pendingPermissionContexts, } from '../commands/permissions.js';
|
|
20
20
|
import { showAskUserQuestionDropdowns, pendingQuestionContexts, cancelPendingQuestion, } from '../commands/ask-question.js';
|
|
21
21
|
import { showActionButtons, waitForQueuedActionButtonsRequest, pendingActionButtonContexts, cancelPendingActionButtons, } from '../commands/action-buttons.js';
|
|
22
22
|
import { pendingFileUploadContexts, cancelPendingFileUpload, } from '../commands/file-upload.js';
|
|
@@ -1904,7 +1904,7 @@ export class ThreadSessionRuntime {
|
|
|
1904
1904
|
if (!pending) {
|
|
1905
1905
|
return;
|
|
1906
1906
|
}
|
|
1907
|
-
|
|
1907
|
+
pendingPermissionContexts.delete(pending.contextHash);
|
|
1908
1908
|
threadPermissions.delete(properties.requestID);
|
|
1909
1909
|
if (threadPermissions.size === 0) {
|
|
1910
1910
|
pendingPermissions.delete(this.thread.id);
|
|
@@ -2267,6 +2267,11 @@ export class ThreadSessionRuntime {
|
|
|
2267
2267
|
const variantField = thinkingValue
|
|
2268
2268
|
? { variant: thinkingValue }
|
|
2269
2269
|
: {};
|
|
2270
|
+
await this.sendNewSessionModelInfo({
|
|
2271
|
+
createdNewSession,
|
|
2272
|
+
model: modelField,
|
|
2273
|
+
agent: resolvedAgent,
|
|
2274
|
+
});
|
|
2270
2275
|
// ── Build prompt parts ──────────────────────────────────
|
|
2271
2276
|
const images = input.images || [];
|
|
2272
2277
|
const promptWithImagePaths = (() => {
|
|
@@ -2837,6 +2842,11 @@ export class ThreadSessionRuntime {
|
|
|
2837
2842
|
providerID: earlyModelParam.providerID,
|
|
2838
2843
|
modelID: earlyModelParam.modelID,
|
|
2839
2844
|
});
|
|
2845
|
+
await this.sendNewSessionModelInfo({
|
|
2846
|
+
createdNewSession,
|
|
2847
|
+
model: earlyModelParam,
|
|
2848
|
+
agent: earlyAgentPreference,
|
|
2849
|
+
});
|
|
2840
2850
|
// ── Build prompt parts ────────────────────────────────────
|
|
2841
2851
|
const images = input.images || [];
|
|
2842
2852
|
const promptWithImagePaths = (() => {
|
|
@@ -2935,6 +2945,7 @@ export class ThreadSessionRuntime {
|
|
|
2935
2945
|
command: queuedCommand.name,
|
|
2936
2946
|
arguments: queuedCommand.arguments + (discordTag ? `\n${discordTag}` : ''),
|
|
2937
2947
|
agent: earlyAgentPreference,
|
|
2948
|
+
model: `${earlyModelParam.providerID}/${earlyModelParam.modelID}`,
|
|
2938
2949
|
...variantField,
|
|
2939
2950
|
}, { signal: commandSignal });
|
|
2940
2951
|
});
|
|
@@ -3135,6 +3146,25 @@ export class ThreadSessionRuntime {
|
|
|
3135
3146
|
}
|
|
3136
3147
|
return { session, getClient, createdNewSession };
|
|
3137
3148
|
}
|
|
3149
|
+
/**
|
|
3150
|
+
* Emit the model + agent banner once, before the first prompt or OpenCode
|
|
3151
|
+
* command can produce visible output in a newly-created session thread.
|
|
3152
|
+
*/
|
|
3153
|
+
async sendNewSessionModelInfo({ createdNewSession, model, agent, }) {
|
|
3154
|
+
if (!createdNewSession) {
|
|
3155
|
+
return;
|
|
3156
|
+
}
|
|
3157
|
+
const modelLabel = `${model.providerID}/${model.modelID}`;
|
|
3158
|
+
const agentLabel = agent && agent.toLowerCase() !== 'build'
|
|
3159
|
+
? ` ⋅ ${agent}`
|
|
3160
|
+
: '';
|
|
3161
|
+
const result = await errore.tryAsync(() => {
|
|
3162
|
+
return sendThreadMessage(this.thread, `*using ${modelLabel}${agentLabel}*`, { flags: SILENT_MESSAGE_FLAGS });
|
|
3163
|
+
});
|
|
3164
|
+
if (result instanceof Error) {
|
|
3165
|
+
logger.warn(`[SESSION INFO] Failed to send model info: ${result.message}`);
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3138
3168
|
/**
|
|
3139
3169
|
* Emit the run footer: duration, model, context%, project info.
|
|
3140
3170
|
* Triggered directly from the terminal assistant message.updated event so the
|