kimaki 0.26.0 → 0.28.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 +250 -164
- package/dist/anthropic-account-identity.js +9 -1
- package/dist/anthropic-auth-plugin.js +10 -13
- package/dist/anthropic-auth-plugin.test.js +17 -0
- package/dist/anthropic-auth-state.js +1 -0
- package/dist/bash-tool-schema-plugin.js +32 -0
- package/dist/bash-tool-schema-plugin.test.js +49 -0
- package/dist/bin.js +73 -19
- package/dist/cli-commands/maintenance.js +46 -7
- package/dist/cli-commands/project.js +11 -27
- package/dist/cli-commands/send.js +30 -1
- package/dist/cli-commands/session.js +322 -90
- package/dist/cli-commands/task.js +34 -15
- package/dist/cli-parsing.test.js +83 -4
- package/dist/cli-runner.js +44 -8
- package/dist/cli-runner.test.js +27 -0
- package/dist/cli-send-thread.e2e.test.js +9 -9
- package/dist/cli.js +32 -0
- package/dist/commands/abort.js +5 -1
- package/dist/commands/action-buttons.js +2 -1
- package/dist/commands/add-project.js +4 -7
- package/dist/commands/agent.js +164 -32
- package/dist/commands/ask-question.js +23 -33
- package/dist/commands/ask-question.test.js +43 -1
- package/dist/commands/btw.js +28 -6
- package/dist/commands/file-upload.js +2 -1
- package/dist/commands/fork-subagent.js +2 -4
- package/dist/commands/fork.js +4 -4
- package/dist/commands/last-sessions.js +17 -10
- package/dist/commands/login.js +6 -0
- package/dist/commands/merge-worktree.js +47 -14
- package/dist/commands/model-variant.js +8 -1
- package/dist/commands/model.js +16 -9
- package/dist/commands/multioauth.js +1 -0
- package/dist/commands/new-worktree.js +170 -102
- package/dist/commands/queue.js +3 -2
- package/dist/commands/resume.js +4 -4
- package/dist/commands/session.js +78 -63
- package/dist/commands/user-command.js +6 -8
- package/dist/commands/worktrees.js +5 -13
- package/dist/config.js +16 -1
- package/dist/cpu-profiler.js +211 -0
- package/dist/cpu-profiler.test.js +160 -0
- package/dist/database.js +152 -3
- package/dist/db.js +33 -0
- package/dist/db.test.js +267 -1
- package/dist/default-channel-provisioning.test.js +148 -0
- package/dist/discord-bot.js +517 -488
- package/dist/discord-command-registration.js +25 -10
- package/dist/discord-command-registration.test.js +17 -0
- package/dist/discord-utils.js +97 -5
- package/dist/discord-utils.test.js +151 -1
- package/dist/errors.js +10 -0
- package/dist/exec-async.js +17 -5
- package/dist/external-directory-permissions.e2e.test.js +11 -7
- package/dist/external-directory-project-deny.e2e.test.js +5 -3
- package/dist/external-opencode-sync.js +94 -17
- package/dist/external-opencode-sync.test.js +206 -0
- package/dist/extract-sdk-error-message.test.js +13 -0
- package/dist/file-edit-log.js +197 -0
- package/dist/file-edit-log.test.js +258 -0
- package/dist/format-tables.js +52 -0
- package/dist/format-tables.test.js +93 -0
- package/dist/gateway-proxy-reconnect.e2e.test.js +28 -8
- package/dist/gateway-proxy.e2e.test.js +18 -18
- package/dist/git-worktree-core.js +182 -17
- package/dist/interaction-handler.js +416 -398
- package/dist/ipc-tools-plugin.js +68 -3
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +122 -58
- package/dist/kimaki-opencode-plugin.js +9 -3
- package/dist/kimaki-workspace-adaptor.js +33 -10
- package/dist/logger.js +11 -1
- package/dist/markdown.js +3 -1
- package/dist/markdown.test.js +11 -3
- package/dist/message-formatting.js +181 -51
- package/dist/message-formatting.test.js +529 -22
- package/dist/message-preprocessing.js +105 -1
- package/dist/oauth-rotation-shared.js +10 -0
- package/dist/openai-auth-plugin.js +1 -0
- package/dist/openai-auth-state.js +1 -0
- package/dist/opencode-command.js +28 -0
- package/dist/opencode-command.test.js +109 -1
- package/dist/opencode-interrupt-plugin.js +4 -0
- package/dist/opencode-interrupt-plugin.test.js +24 -0
- package/dist/opencode.js +82 -15
- package/dist/project-add.test.js +62 -0
- package/dist/question-abort-clears-dropdown.e2e.test.js +89 -0
- package/dist/question-abort-retry-text-before-dropdown.e2e.test.js +150 -0
- package/dist/question-text-before-dropdown.e2e.test.js +52 -0
- package/dist/queue-advanced-abort.e2e.test.js +8 -10
- package/dist/queue-advanced-action-buttons.e2e.test.js +8 -8
- package/dist/queue-advanced-e2e-setup.js +78 -5
- package/dist/queue-advanced-footer.e2e.test.js +60 -269
- package/dist/queue-advanced-model-switch.e2e.test.js +9 -11
- package/dist/queue-advanced-permissions-typing.e2e.test.js +15 -15
- package/dist/queue-advanced-question.e2e.test.js +32 -27
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +10 -12
- package/dist/queue-advanced-typing.e2e.test.js +7 -9
- package/dist/queue-delete-message.e2e.test.js +3 -3
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +5 -5
- package/dist/queue-interrupt-drain.e2e.test.js +122 -12
- package/dist/queue-question-select-drain.e2e.test.js +34 -132
- package/dist/runtime-lifecycle.e2e.test.js +21 -51
- package/dist/schema.js +35 -1
- package/dist/session-handler/event-stream-state.js +391 -50
- package/dist/session-handler/event-stream-state.test.js +736 -8
- package/dist/session-handler/model-utils.js +172 -5
- package/dist/session-handler/model-utils.test.js +445 -0
- package/dist/session-handler/opencode-session-event-log.js +30 -1
- package/dist/session-handler/thread-ingress.test.js +41 -0
- package/dist/session-handler/thread-runtime-state.js +2 -0
- package/dist/session-handler/thread-session-runtime-summary.test.js +121 -0
- package/dist/session-handler/thread-session-runtime.js +574 -299
- package/dist/session-search.js +123 -4
- package/dist/session-search.test.js +248 -1
- package/dist/session-sleep.test.js +224 -0
- package/dist/session-title-rename.test.js +37 -39
- package/dist/sleep.e2e.test.js +494 -0
- package/dist/startup-time.e2e.test.js +2 -1
- package/dist/store.js +3 -0
- package/dist/system-message.js +102 -16
- package/dist/system-message.test.js +192 -15
- package/dist/task-runner.js +96 -5
- package/dist/task-schedule.js +87 -1
- package/dist/task-schedule.test.js +149 -1
- package/dist/test-utils.js +17 -9
- package/dist/thinking-utils.js +10 -0
- package/dist/thinking-utils.test.js +35 -0
- package/dist/thread-message-queue.e2e.test.js +65 -213
- package/dist/undo-redo.e2e.test.js +17 -9
- package/dist/voice-attachment.js +23 -1
- package/dist/voice-handler.js +21 -4
- package/dist/voice-message.e2e.test.js +144 -30
- package/dist/voice-routing-input.e2e.test.js +195 -0
- package/dist/voice-routing-question.e2e.test.js +125 -0
- package/dist/voice.js +28 -8
- package/dist/voice.test.js +96 -30
- package/dist/worktree-clone-isolation.e2e.test.js +188 -0
- package/dist/worktree-lifecycle.e2e.test.js +55 -36
- package/dist/worktrees.js +226 -443
- package/dist/worktrees.test.js +435 -58
- package/dist/xai-auth-plugin.js +1 -0
- package/dist/xai-auth-state.js +1 -0
- package/package.json +8 -6
- package/skills/bump-holocron-consumers/SKILL.md +137 -0
- package/skills/critique/SKILL.md +4 -4
- package/skills/errore/SKILL.md +60 -218
- package/skills/goke/SKILL.md +150 -10
- package/skills/holocron/SKILL.md +154 -17
- package/skills/npm-package/SKILL.md +55 -35
- package/skills/profano/SKILL.md +17 -2
- package/skills/spiceflow/SKILL.md +39 -3
- package/skills/strada/SKILL.md +39 -0
- package/skills/zele/SKILL.md +19 -0
- package/src/agent-model.e2e.test.ts +287 -186
- package/src/anthropic-account-identity.ts +20 -1
- package/src/anthropic-auth-plugin.test.ts +19 -0
- package/src/anthropic-auth-plugin.ts +12 -16
- package/src/anthropic-auth-state.ts +1 -0
- package/src/bash-tool-schema-plugin.test.ts +55 -0
- package/src/bash-tool-schema-plugin.ts +46 -0
- package/src/bin.ts +78 -26
- package/src/cli-commands/maintenance.ts +59 -13
- package/src/cli-commands/project.ts +14 -35
- package/src/cli-commands/send.ts +31 -1
- package/src/cli-commands/session.ts +374 -114
- package/src/cli-commands/task.ts +34 -15
- package/src/cli-parsing.test.ts +103 -4
- package/src/cli-runner.test.ts +49 -0
- package/src/cli-runner.ts +44 -7
- package/src/cli-send-thread.e2e.test.ts +9 -11
- package/src/cli.ts +53 -0
- package/src/commands/abort.ts +6 -1
- package/src/commands/action-buttons.ts +2 -1
- package/src/commands/add-project.ts +4 -8
- package/src/commands/agent.ts +205 -29
- package/src/commands/ask-question.test.ts +48 -1
- package/src/commands/ask-question.ts +23 -33
- package/src/commands/btw.ts +40 -5
- package/src/commands/file-upload.ts +2 -1
- package/src/commands/fork-subagent.ts +2 -3
- package/src/commands/fork.ts +4 -3
- package/src/commands/last-sessions.ts +10 -4
- package/src/commands/login.ts +6 -0
- package/src/commands/merge-worktree.ts +54 -16
- package/src/commands/model-variant.ts +9 -1
- package/src/commands/model.ts +20 -9
- package/src/commands/multioauth.ts +1 -0
- package/src/commands/new-worktree.ts +138 -55
- package/src/commands/queue.ts +3 -2
- package/src/commands/resume.ts +4 -3
- package/src/commands/session.ts +94 -70
- package/src/commands/user-command.ts +6 -6
- package/src/commands/worktrees.ts +5 -12
- package/src/config.ts +18 -1
- package/src/cpu-profiler.test.ts +183 -0
- package/src/cpu-profiler.ts +293 -0
- package/src/database.ts +235 -3
- package/src/db.test.ts +293 -0
- package/src/db.ts +38 -0
- package/src/default-channel-provisioning.test.ts +155 -0
- package/src/discord-bot.ts +78 -36
- package/src/discord-command-registration.test.ts +20 -0
- package/src/discord-command-registration.ts +42 -12
- package/src/discord-utils.test.ts +176 -0
- package/src/discord-utils.ts +152 -7
- package/src/errors.ts +12 -0
- package/src/exec-async.ts +20 -11
- package/src/external-directory-permissions.e2e.test.ts +11 -7
- package/src/external-directory-project-deny.e2e.test.ts +5 -3
- package/src/external-opencode-sync.test.ts +248 -0
- package/src/external-opencode-sync.ts +122 -19
- package/src/extract-sdk-error-message.test.ts +18 -0
- package/src/file-edit-log.test.ts +287 -0
- package/src/file-edit-log.ts +283 -0
- package/src/format-tables.test.ts +101 -0
- package/src/format-tables.ts +75 -0
- package/src/gateway-proxy-reconnect.e2e.test.ts +38 -8
- package/src/gateway-proxy.e2e.test.ts +18 -17
- package/src/git-worktree-core.ts +247 -20
- package/src/interaction-handler.ts +36 -13
- package/src/ipc-tools-plugin.ts +82 -5
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +188 -97
- package/src/kimaki-opencode-plugin.ts +9 -3
- package/src/kimaki-workspace-adaptor.ts +40 -11
- package/src/logger.ts +14 -4
- package/src/markdown.test.ts +13 -3
- package/src/markdown.ts +4 -1
- package/src/message-formatting.test.ts +672 -25
- package/src/message-formatting.ts +242 -53
- package/src/message-preprocessing.ts +116 -1
- package/src/oauth-rotation-shared.ts +10 -0
- package/src/openai-auth-plugin.ts +1 -0
- package/src/openai-auth-state.ts +1 -0
- package/src/opencode-command.test.ts +140 -0
- package/src/opencode-command.ts +38 -0
- package/src/opencode-interrupt-plugin.test.ts +32 -0
- package/src/opencode-interrupt-plugin.ts +3 -0
- package/src/opencode.ts +115 -15
- package/src/project-add.test.ts +80 -0
- package/src/question-abort-clears-dropdown.e2e.test.ts +109 -0
- package/src/question-abort-retry-text-before-dropdown.e2e.test.ts +172 -0
- package/src/question-text-before-dropdown.e2e.test.ts +67 -0
- package/src/queue-advanced-abort.e2e.test.ts +8 -9
- package/src/queue-advanced-action-buttons.e2e.test.ts +8 -8
- package/src/queue-advanced-e2e-setup.ts +83 -4
- package/src/queue-advanced-footer.e2e.test.ts +67 -315
- package/src/queue-advanced-model-switch.e2e.test.ts +9 -10
- package/src/queue-advanced-permissions-typing.e2e.test.ts +15 -15
- package/src/queue-advanced-question.e2e.test.ts +36 -30
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +11 -11
- package/src/queue-advanced-typing.e2e.test.ts +8 -8
- package/src/queue-delete-message.e2e.test.ts +3 -3
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +5 -5
- package/src/queue-interrupt-drain.e2e.test.ts +133 -12
- package/src/queue-question-select-drain.e2e.test.ts +36 -161
- package/src/runtime-lifecycle.e2e.test.ts +21 -54
- package/src/schema.sql +15 -2
- package/src/schema.ts +37 -1
- package/src/session-handler/event-stream-state.test.ts +790 -7
- package/src/session-handler/event-stream-state.ts +568 -53
- package/src/session-handler/model-utils.test.ts +537 -0
- package/src/session-handler/model-utils.ts +281 -5
- package/src/session-handler/opencode-session-event-log.ts +31 -1
- package/src/session-handler/thread-ingress.test.ts +54 -0
- package/src/session-handler/thread-runtime-state.ts +3 -0
- package/src/session-handler/thread-session-runtime-summary.test.ts +169 -0
- package/src/session-handler/thread-session-runtime.ts +712 -358
- package/src/session-search.test.ts +281 -0
- package/src/session-search.ts +241 -7
- package/src/session-sleep.test.ts +294 -0
- package/src/session-title-rename.test.ts +54 -48
- package/src/sleep.e2e.test.ts +565 -0
- package/src/startup-time.e2e.test.ts +5 -1
- package/src/store.ts +22 -0
- package/src/system-message.test.ts +226 -15
- package/src/system-message.ts +130 -16
- package/src/task-runner.ts +138 -3
- package/src/task-schedule.test.ts +170 -0
- package/src/task-schedule.ts +141 -1
- package/src/test-utils.ts +21 -10
- package/src/thinking-utils.test.ts +46 -0
- package/src/thinking-utils.ts +21 -0
- package/src/thread-message-queue.e2e.test.ts +65 -245
- package/src/undo-redo.e2e.test.ts +17 -9
- package/src/voice-attachment.ts +29 -1
- package/src/voice-handler.ts +23 -4
- package/src/voice-message.e2e.test.ts +149 -29
- package/src/voice-routing-input.e2e.test.ts +203 -0
- package/src/voice-routing-question.e2e.test.ts +131 -0
- package/src/voice.test.ts +107 -29
- package/src/voice.ts +34 -6
- package/src/worktree-clone-isolation.e2e.test.ts +213 -0
- package/src/worktree-lifecycle.e2e.test.ts +55 -35
- package/src/worktrees.test.ts +505 -64
- package/src/worktrees.ts +257 -603
- package/src/xai-auth-plugin.ts +1 -0
- package/src/xai-auth-state.ts +1 -0
- package/dist/message-finish-field.e2e.test.js +0 -169
- package/dist/subagent-rate-limit-plugin.js +0 -184
- package/src/message-finish-field.e2e.test.ts +0 -196
- package/src/subagent-rate-limit-plugin.ts +0 -228
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// Tests for file-edit event extraction, derivation, and JSONL persistence.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { afterEach, describe, expect, test } from 'vitest';
|
|
6
|
+
import { appendFileEditEvents, createFileEditHooks, editorsForFile, extractEditedFiles, loadFileEditEvents, } from './file-edit-log.js';
|
|
7
|
+
const tempDirs = [];
|
|
8
|
+
function makeDataDir() {
|
|
9
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'kimaki-file-edit-log-'));
|
|
10
|
+
tempDirs.push(dir);
|
|
11
|
+
return dir;
|
|
12
|
+
}
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
for (const dir of tempDirs.splice(0)) {
|
|
15
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
function event(partial) {
|
|
19
|
+
return {
|
|
20
|
+
v: 1,
|
|
21
|
+
at: 1,
|
|
22
|
+
tool: 'edit',
|
|
23
|
+
...partial,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
describe('extractEditedFiles', () => {
|
|
27
|
+
test('extracts edit and write filePath and ignores other tools', () => {
|
|
28
|
+
expect(extractEditedFiles({
|
|
29
|
+
tool: 'edit',
|
|
30
|
+
args: { filePath: 'src/foo.ts' },
|
|
31
|
+
directory: '/repo',
|
|
32
|
+
})).toMatchInlineSnapshot(`
|
|
33
|
+
[
|
|
34
|
+
"/repo/src/foo.ts",
|
|
35
|
+
]
|
|
36
|
+
`);
|
|
37
|
+
expect(extractEditedFiles({
|
|
38
|
+
tool: 'write',
|
|
39
|
+
args: { filePath: '/abs/bar.ts' },
|
|
40
|
+
directory: '/repo',
|
|
41
|
+
})).toMatchInlineSnapshot(`
|
|
42
|
+
[
|
|
43
|
+
"/abs/bar.ts",
|
|
44
|
+
]
|
|
45
|
+
`);
|
|
46
|
+
expect(extractEditedFiles({
|
|
47
|
+
tool: 'read',
|
|
48
|
+
args: { filePath: 'src/foo.ts' },
|
|
49
|
+
directory: '/repo',
|
|
50
|
+
})).toMatchInlineSnapshot(`
|
|
51
|
+
[]
|
|
52
|
+
`);
|
|
53
|
+
expect(extractEditedFiles({
|
|
54
|
+
tool: 'bash',
|
|
55
|
+
args: {},
|
|
56
|
+
directory: '/repo',
|
|
57
|
+
})).toMatchInlineSnapshot(`
|
|
58
|
+
[]
|
|
59
|
+
`);
|
|
60
|
+
});
|
|
61
|
+
test('extracts apply_patch paths and resolves them against directory', () => {
|
|
62
|
+
const patchText = [
|
|
63
|
+
'*** Begin Patch',
|
|
64
|
+
'*** Update File: src/foo.ts',
|
|
65
|
+
'@@',
|
|
66
|
+
'-a',
|
|
67
|
+
'+b',
|
|
68
|
+
'*** Add File: src/bar.ts',
|
|
69
|
+
'+hello',
|
|
70
|
+
'*** Delete File: src/old.ts',
|
|
71
|
+
'*** Move to: src/renamed.ts',
|
|
72
|
+
'*** End Patch',
|
|
73
|
+
].join('\n');
|
|
74
|
+
expect(extractEditedFiles({
|
|
75
|
+
tool: 'apply_patch',
|
|
76
|
+
args: { patchText },
|
|
77
|
+
directory: '/repo',
|
|
78
|
+
})).toMatchInlineSnapshot(`
|
|
79
|
+
[
|
|
80
|
+
"/repo/src/foo.ts",
|
|
81
|
+
"/repo/src/bar.ts",
|
|
82
|
+
"/repo/src/old.ts",
|
|
83
|
+
"/repo/src/renamed.ts",
|
|
84
|
+
]
|
|
85
|
+
`);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
describe('editorsForFile', () => {
|
|
89
|
+
test('orders unique sessions by last edit and matches relative paths', () => {
|
|
90
|
+
const events = [
|
|
91
|
+
event({ sessionId: 'ses_old', file: '/repo/src/foo.ts', at: 10 }),
|
|
92
|
+
event({ sessionId: 'ses_other', file: '/repo/src/other.ts', at: 50 }),
|
|
93
|
+
event({ sessionId: 'ses_new', file: '/repo/src/foo.ts', at: 20 }),
|
|
94
|
+
event({ sessionId: 'ses_old', file: '/repo/src/foo.ts', at: 40, tool: 'write' }),
|
|
95
|
+
];
|
|
96
|
+
expect(editorsForFile({
|
|
97
|
+
events,
|
|
98
|
+
filePath: 'src/foo.ts',
|
|
99
|
+
cwd: '/repo',
|
|
100
|
+
})).toMatchInlineSnapshot(`
|
|
101
|
+
[
|
|
102
|
+
{
|
|
103
|
+
"at": 40,
|
|
104
|
+
"sessionId": "ses_old",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"at": 20,
|
|
108
|
+
"sessionId": "ses_new",
|
|
109
|
+
},
|
|
110
|
+
]
|
|
111
|
+
`);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('file edit jsonl log', () => {
|
|
115
|
+
test('appends events and loads them back', () => {
|
|
116
|
+
const dataDir = makeDataDir();
|
|
117
|
+
const first = [
|
|
118
|
+
event({ sessionId: 'ses_a', file: '/repo/a.ts', at: 1 }),
|
|
119
|
+
];
|
|
120
|
+
const second = [
|
|
121
|
+
event({ sessionId: 'ses_b', file: '/repo/b.ts', at: 2, tool: 'write' }),
|
|
122
|
+
];
|
|
123
|
+
const appended = appendFileEditEvents({ dataDir, events: first });
|
|
124
|
+
if (appended instanceof Error)
|
|
125
|
+
throw appended;
|
|
126
|
+
const appendedAgain = appendFileEditEvents({ dataDir, events: second });
|
|
127
|
+
if (appendedAgain instanceof Error)
|
|
128
|
+
throw appendedAgain;
|
|
129
|
+
const loaded = loadFileEditEvents({ dataDir });
|
|
130
|
+
if (loaded instanceof Error)
|
|
131
|
+
throw loaded;
|
|
132
|
+
expect(loaded).toMatchInlineSnapshot(`
|
|
133
|
+
[
|
|
134
|
+
{
|
|
135
|
+
"at": 1,
|
|
136
|
+
"file": "/repo/a.ts",
|
|
137
|
+
"sessionId": "ses_a",
|
|
138
|
+
"tool": "edit",
|
|
139
|
+
"v": 1,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"at": 2,
|
|
143
|
+
"file": "/repo/b.ts",
|
|
144
|
+
"sessionId": "ses_b",
|
|
145
|
+
"tool": "write",
|
|
146
|
+
"v": 1,
|
|
147
|
+
},
|
|
148
|
+
]
|
|
149
|
+
`);
|
|
150
|
+
});
|
|
151
|
+
test('compacts to the newest events when the log is too large', () => {
|
|
152
|
+
const dataDir = makeDataDir();
|
|
153
|
+
const events = [
|
|
154
|
+
event({ sessionId: 'ses_1', file: '/repo/a.ts', at: 1 }),
|
|
155
|
+
event({ sessionId: 'ses_2', file: '/repo/b.ts', at: 2 }),
|
|
156
|
+
event({ sessionId: 'ses_3', file: '/repo/c.ts', at: 3 }),
|
|
157
|
+
];
|
|
158
|
+
const appended = appendFileEditEvents({
|
|
159
|
+
dataDir,
|
|
160
|
+
events,
|
|
161
|
+
maxEvents: 2,
|
|
162
|
+
compactAfterBytes: 1,
|
|
163
|
+
});
|
|
164
|
+
if (appended instanceof Error)
|
|
165
|
+
throw appended;
|
|
166
|
+
const loaded = loadFileEditEvents({ dataDir });
|
|
167
|
+
if (loaded instanceof Error)
|
|
168
|
+
throw loaded;
|
|
169
|
+
expect(loaded).toMatchInlineSnapshot(`
|
|
170
|
+
[
|
|
171
|
+
{
|
|
172
|
+
"at": 2,
|
|
173
|
+
"file": "/repo/b.ts",
|
|
174
|
+
"sessionId": "ses_2",
|
|
175
|
+
"tool": "edit",
|
|
176
|
+
"v": 1,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"at": 3,
|
|
180
|
+
"file": "/repo/c.ts",
|
|
181
|
+
"sessionId": "ses_3",
|
|
182
|
+
"tool": "edit",
|
|
183
|
+
"v": 1,
|
|
184
|
+
},
|
|
185
|
+
]
|
|
186
|
+
`);
|
|
187
|
+
});
|
|
188
|
+
test('compacts repeated edits of the same session and file to one row', () => {
|
|
189
|
+
const dataDir = makeDataDir();
|
|
190
|
+
const events = [
|
|
191
|
+
event({ sessionId: 'ses_a', file: '/repo/a.ts', at: 1 }),
|
|
192
|
+
event({ sessionId: 'ses_a', file: '/repo/a.ts', at: 2, tool: 'write' }),
|
|
193
|
+
event({ sessionId: 'ses_a', file: '/repo/a.ts', at: 3 }),
|
|
194
|
+
];
|
|
195
|
+
const appended = appendFileEditEvents({
|
|
196
|
+
dataDir,
|
|
197
|
+
events,
|
|
198
|
+
maxEvents: 10,
|
|
199
|
+
compactAfterBytes: 1,
|
|
200
|
+
});
|
|
201
|
+
if (appended instanceof Error)
|
|
202
|
+
throw appended;
|
|
203
|
+
const loaded = loadFileEditEvents({ dataDir });
|
|
204
|
+
if (loaded instanceof Error)
|
|
205
|
+
throw loaded;
|
|
206
|
+
expect(loaded).toMatchInlineSnapshot(`
|
|
207
|
+
[
|
|
208
|
+
{
|
|
209
|
+
"at": 3,
|
|
210
|
+
"file": "/repo/a.ts",
|
|
211
|
+
"sessionId": "ses_a",
|
|
212
|
+
"tool": "edit",
|
|
213
|
+
"v": 1,
|
|
214
|
+
},
|
|
215
|
+
]
|
|
216
|
+
`);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
describe('fileEditTrackerPlugin', () => {
|
|
220
|
+
test('records edit and apply_patch and ignores bash', async () => {
|
|
221
|
+
const dataDir = makeDataDir();
|
|
222
|
+
const hooks = createFileEditHooks({ dataDir, directory: '/repo' });
|
|
223
|
+
await hooks['tool.execute.after']({
|
|
224
|
+
tool: 'edit',
|
|
225
|
+
sessionID: 'ses_x',
|
|
226
|
+
args: { filePath: 'src/a.ts' },
|
|
227
|
+
});
|
|
228
|
+
await hooks['tool.execute.after']({
|
|
229
|
+
tool: 'apply_patch',
|
|
230
|
+
sessionID: 'ses_x',
|
|
231
|
+
args: { patchText: '*** Update File: src/b.ts\n' },
|
|
232
|
+
});
|
|
233
|
+
await hooks['tool.execute.after']({
|
|
234
|
+
tool: 'bash',
|
|
235
|
+
sessionID: 'ses_x',
|
|
236
|
+
args: {},
|
|
237
|
+
});
|
|
238
|
+
const loaded = loadFileEditEvents({ dataDir });
|
|
239
|
+
if (loaded instanceof Error)
|
|
240
|
+
throw loaded;
|
|
241
|
+
expect(loaded.map((entry) => {
|
|
242
|
+
return { sessionId: entry.sessionId, file: entry.file, tool: entry.tool };
|
|
243
|
+
})).toMatchInlineSnapshot(`
|
|
244
|
+
[
|
|
245
|
+
{
|
|
246
|
+
"file": "/repo/src/a.ts",
|
|
247
|
+
"sessionId": "ses_x",
|
|
248
|
+
"tool": "edit",
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"file": "/repo/src/b.ts",
|
|
252
|
+
"sessionId": "ses_x",
|
|
253
|
+
"tool": "apply_patch",
|
|
254
|
+
},
|
|
255
|
+
]
|
|
256
|
+
`);
|
|
257
|
+
});
|
|
258
|
+
});
|
package/dist/format-tables.js
CHANGED
|
@@ -173,8 +173,30 @@ function splitMarkdownByCallouts({ markdown, }) {
|
|
|
173
173
|
}) ?? [markdown];
|
|
174
174
|
const blocks = [];
|
|
175
175
|
let textBuffer = '';
|
|
176
|
+
let fence = null;
|
|
176
177
|
for (let index = 0; index < lines.length; index++) {
|
|
177
178
|
const line = lines[index];
|
|
179
|
+
if (fence) {
|
|
180
|
+
textBuffer += line;
|
|
181
|
+
if (isMarkdownFenceClose({ line, fence }))
|
|
182
|
+
fence = null;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const openingFence = parseMarkdownFenceOpen({ line });
|
|
186
|
+
if (openingFence) {
|
|
187
|
+
fence = openingFence;
|
|
188
|
+
textBuffer += line;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const singleLineCallout = parseSingleLineCallout({ line });
|
|
192
|
+
if (singleLineCallout) {
|
|
193
|
+
if (textBuffer.length > 0) {
|
|
194
|
+
blocks.push({ type: 'text', text: textBuffer });
|
|
195
|
+
textBuffer = '';
|
|
196
|
+
}
|
|
197
|
+
blocks.push(singleLineCallout);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
178
200
|
const callout = parseCalloutOpenLine({ line });
|
|
179
201
|
if (!callout) {
|
|
180
202
|
textBuffer += line;
|
|
@@ -338,6 +360,36 @@ function collectCalloutBodyFromLines({ lines, startIndex, }) {
|
|
|
338
360
|
}
|
|
339
361
|
return new Error('Unclosed <callout> block');
|
|
340
362
|
}
|
|
363
|
+
function parseMarkdownFenceOpen({ line, }) {
|
|
364
|
+
const marker = line.match(/^ {0,3}(`{3,}|~{3,})/)?.[1];
|
|
365
|
+
if (!marker)
|
|
366
|
+
return null;
|
|
367
|
+
return {
|
|
368
|
+
marker: marker.startsWith('`') ? '`' : '~',
|
|
369
|
+
length: marker.length,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function isMarkdownFenceClose({ line, fence, }) {
|
|
373
|
+
const marker = line.trim();
|
|
374
|
+
return marker.length >= fence.length &&
|
|
375
|
+
[...marker].every((character) => character === fence.marker);
|
|
376
|
+
}
|
|
377
|
+
function parseSingleLineCallout({ line, }) {
|
|
378
|
+
const match = line.trim().match(/^(<callout(?:\s+[^>]*)?>)(.*?)<\/callout>$/i);
|
|
379
|
+
if (!match)
|
|
380
|
+
return null;
|
|
381
|
+
const content = match[2];
|
|
382
|
+
if (/<\/?callout(?:\s|>)/i.test(content))
|
|
383
|
+
return null;
|
|
384
|
+
const callout = parseCalloutOpenLine({ line: match[1] });
|
|
385
|
+
if (!callout)
|
|
386
|
+
return null;
|
|
387
|
+
return {
|
|
388
|
+
type: 'callout',
|
|
389
|
+
content,
|
|
390
|
+
callout,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
341
393
|
function parseCalloutOpenLine({ line, }) {
|
|
342
394
|
const match = line.trim().match(/^<callout(?:\s+[^>]*)?>$/i);
|
|
343
395
|
if (!match) {
|
|
@@ -409,6 +409,99 @@ Done.`);
|
|
|
409
409
|
]
|
|
410
410
|
`);
|
|
411
411
|
});
|
|
412
|
+
test('renders a single-line callout inside an accented container', () => {
|
|
413
|
+
const result = splitTablesFromMarkdown(`<callout accent="#f59e0b">Full suite: **622 passed, 6 unrelated existing failures, 8 skipped**.</callout>`);
|
|
414
|
+
expect(result.map((segment) => segment.type)).toEqual(['components']);
|
|
415
|
+
expect(result).toMatchInlineSnapshot(`
|
|
416
|
+
[
|
|
417
|
+
{
|
|
418
|
+
"components": [
|
|
419
|
+
{
|
|
420
|
+
"accent_color": 16096779,
|
|
421
|
+
"components": [
|
|
422
|
+
{
|
|
423
|
+
"content": "Full suite: **622 passed, 6 unrelated existing failures, 8 skipped**.",
|
|
424
|
+
"type": 10,
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
"type": 17,
|
|
428
|
+
},
|
|
429
|
+
],
|
|
430
|
+
"type": "components",
|
|
431
|
+
},
|
|
432
|
+
]
|
|
433
|
+
`);
|
|
434
|
+
});
|
|
435
|
+
test('splits text around a single-line callout into separate segments', () => {
|
|
436
|
+
const result = splitTablesFromMarkdown(`Done.
|
|
437
|
+
|
|
438
|
+
<callout accent="#f59e0b">Confidence: high.</callout>
|
|
439
|
+
|
|
440
|
+
Diff: https://critique.work/v/example`);
|
|
441
|
+
expect(result.map((segment) => segment.type)).toEqual([
|
|
442
|
+
'text',
|
|
443
|
+
'components',
|
|
444
|
+
'text',
|
|
445
|
+
]);
|
|
446
|
+
expect(result).toMatchInlineSnapshot(`
|
|
447
|
+
[
|
|
448
|
+
{
|
|
449
|
+
"text": "Done.
|
|
450
|
+
|
|
451
|
+
",
|
|
452
|
+
"type": "text",
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
"components": [
|
|
456
|
+
{
|
|
457
|
+
"accent_color": 16096779,
|
|
458
|
+
"components": [
|
|
459
|
+
{
|
|
460
|
+
"content": "Confidence: high.",
|
|
461
|
+
"type": 10,
|
|
462
|
+
},
|
|
463
|
+
],
|
|
464
|
+
"type": 17,
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
"type": "components",
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
"text": "
|
|
471
|
+
Diff: https://critique.work/v/example",
|
|
472
|
+
"type": "text",
|
|
473
|
+
},
|
|
474
|
+
]
|
|
475
|
+
`);
|
|
476
|
+
});
|
|
477
|
+
test('preserves single-line callout syntax inside fenced code', () => {
|
|
478
|
+
const result = splitTablesFromMarkdown(`\`\`\`html
|
|
479
|
+
<callout accent="#f59e0b">inside</callout>
|
|
480
|
+
\`\`\``);
|
|
481
|
+
expect(result.map((segment) => segment.type)).toEqual(['text']);
|
|
482
|
+
expect(result).toMatchInlineSnapshot(`
|
|
483
|
+
[
|
|
484
|
+
{
|
|
485
|
+
"text": "\`\`\`html
|
|
486
|
+
<callout accent="#f59e0b">inside</callout>
|
|
487
|
+
\`\`\`",
|
|
488
|
+
"type": "text",
|
|
489
|
+
},
|
|
490
|
+
]
|
|
491
|
+
`);
|
|
492
|
+
});
|
|
493
|
+
test('keeps malformed single-line callouts as plain text', () => {
|
|
494
|
+
const result = splitTablesFromMarkdown(`<callout accent="#f59e0b">content</callout></callout>`);
|
|
495
|
+
expect(result.map((segment) => segment.type)).toEqual(['text']);
|
|
496
|
+
expect(result).toMatchInlineSnapshot(`
|
|
497
|
+
[
|
|
498
|
+
{
|
|
499
|
+
"text": "<callout accent="#f59e0b">content</callout></callout>",
|
|
500
|
+
"type": "text",
|
|
501
|
+
},
|
|
502
|
+
]
|
|
503
|
+
`);
|
|
504
|
+
});
|
|
412
505
|
test('renders callout text inside an accented container', () => {
|
|
413
506
|
const result = splitTablesFromMarkdown(`<callout accent="#2b7fff">
|
|
414
507
|
## Important
|
|
@@ -238,6 +238,24 @@ function waitForClientReady({ client, token, timeoutMs = 30_000, }) {
|
|
|
238
238
|
client.login(token).catch(reject);
|
|
239
239
|
});
|
|
240
240
|
}
|
|
241
|
+
function waitForGatewayUnavailable({ client, timeoutMs = 10_000, }) {
|
|
242
|
+
return new Promise((resolve, reject) => {
|
|
243
|
+
let reconnectAttempts = 0;
|
|
244
|
+
const timeout = setTimeout(() => {
|
|
245
|
+
client.off(Events.ShardReconnecting, onReconnecting);
|
|
246
|
+
reject(new Error(`Client did not observe gateway unavailability within ${timeoutMs}ms`));
|
|
247
|
+
}, timeoutMs);
|
|
248
|
+
const onReconnecting = () => {
|
|
249
|
+
reconnectAttempts += 1;
|
|
250
|
+
if (reconnectAttempts < 2)
|
|
251
|
+
return;
|
|
252
|
+
clearTimeout(timeout);
|
|
253
|
+
client.off(Events.ShardReconnecting, onReconnecting);
|
|
254
|
+
resolve();
|
|
255
|
+
};
|
|
256
|
+
client.on(Events.ShardReconnecting, onReconnecting);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
241
259
|
function waitForReconnection({ client, events, label, timeoutMs = 30_000, }) {
|
|
242
260
|
return new Promise((resolve) => {
|
|
243
261
|
const timeout = setTimeout(() => {
|
|
@@ -245,14 +263,14 @@ function waitForReconnection({ client, events, label, timeoutMs = 30_000, }) {
|
|
|
245
263
|
console.log(`[${label}] Events so far:`, events);
|
|
246
264
|
resolve(false);
|
|
247
265
|
}, timeoutMs);
|
|
248
|
-
|
|
266
|
+
const finish = () => {
|
|
249
267
|
clearTimeout(timeout);
|
|
268
|
+
client.off(Events.ShardReady, finish);
|
|
269
|
+
client.off(Events.ShardResume, finish);
|
|
250
270
|
resolve(true);
|
|
251
|
-
}
|
|
252
|
-
client.on(Events.
|
|
253
|
-
|
|
254
|
-
resolve(true);
|
|
255
|
-
});
|
|
271
|
+
};
|
|
272
|
+
client.on(Events.ShardReady, finish);
|
|
273
|
+
client.on(Events.ShardResume, finish);
|
|
256
274
|
});
|
|
257
275
|
}
|
|
258
276
|
// ============================================================================
|
|
@@ -315,13 +333,15 @@ describeLocal('gateway-proxy reconnection (local binary)', () => {
|
|
|
315
333
|
console.log('[local] Client ready');
|
|
316
334
|
// Kill proxy
|
|
317
335
|
const firstProxy = proxyProcess;
|
|
336
|
+
const gatewayUnavailable = waitForGatewayUnavailable({ client });
|
|
318
337
|
await killProxy(proxyProcess);
|
|
319
|
-
await
|
|
338
|
+
await gatewayUnavailable;
|
|
320
339
|
// Restart proxy on same port
|
|
340
|
+
const reconnection = waitForReconnection({ client, events, label: 'local' });
|
|
321
341
|
proxyProcess = startProxy(proxyOpts);
|
|
322
342
|
await waitForProxyReady({ port: proxyPort });
|
|
323
343
|
console.log('[local] Proxy restarted');
|
|
324
|
-
const reconnected = await
|
|
344
|
+
const reconnected = await reconnection;
|
|
325
345
|
console.log('[local] All events:', events);
|
|
326
346
|
if (!reconnected) {
|
|
327
347
|
dumpProxyLogs(firstProxy);
|
|
@@ -19,7 +19,7 @@ import { startHranaServer, stopHranaServer } from './hrana-server.js';
|
|
|
19
19
|
import { setBotToken, initDatabase, closeDatabase, setChannelDirectory, } from './database.js';
|
|
20
20
|
import { setDataDir } from './config.js';
|
|
21
21
|
import { startDiscordBot } from './discord-bot.js';
|
|
22
|
-
import { chooseLockPort, cleanupTestSessions, initTestGitRepo, waitForFooterMessage, } from './test-utils.js';
|
|
22
|
+
import { chooseLockPort, cleanupTestSessions, initTestGitRepo, getMessageVisibleText, waitForFooterMessage, } from './test-utils.js';
|
|
23
23
|
import { stopOpencodeServer } from './opencode.js';
|
|
24
24
|
import { createDiscordRest } from './discord-urls.js';
|
|
25
25
|
import { store } from './store.js';
|
|
@@ -323,12 +323,12 @@ describeIf('gateway-proxy e2e', () => {
|
|
|
323
323
|
"--- from: user (proxy-tester)
|
|
324
324
|
hello from gateway proxy test
|
|
325
325
|
--- from: assistant (TestBot)
|
|
326
|
-
*using deterministic-provider/deterministic-v2*
|
|
327
|
-
|
|
328
|
-
*project ⋅ main ⋅
|
|
326
|
+
> *using deterministic-provider/deterministic-v2*
|
|
327
|
+
> gateway-proxy-reply
|
|
328
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@900000000000000001>"
|
|
329
329
|
`);
|
|
330
330
|
expect(reply).toBeDefined();
|
|
331
|
-
expect(reply.
|
|
331
|
+
expect(getMessageVisibleText(reply).trim().length).toBeGreaterThan(0);
|
|
332
332
|
}, 15_000);
|
|
333
333
|
test('follow-up message in thread gets bot reply', async () => {
|
|
334
334
|
const existingMessages = await discord.thread(firstThreadId).getMessages();
|
|
@@ -350,17 +350,17 @@ describeIf('gateway-proxy e2e', () => {
|
|
|
350
350
|
"--- from: user (proxy-tester)
|
|
351
351
|
hello from gateway proxy test
|
|
352
352
|
--- from: assistant (TestBot)
|
|
353
|
-
*using deterministic-provider/deterministic-v2*
|
|
354
|
-
|
|
355
|
-
*project ⋅ main ⋅
|
|
353
|
+
> *using deterministic-provider/deterministic-v2*
|
|
354
|
+
> gateway-proxy-reply
|
|
355
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@900000000000000001>
|
|
356
356
|
--- from: user (proxy-tester)
|
|
357
357
|
follow up through proxy
|
|
358
358
|
--- from: assistant (TestBot)
|
|
359
|
-
|
|
360
|
-
*project ⋅ main ⋅
|
|
359
|
+
> gateway-proxy-reply
|
|
360
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@900000000000000001>"
|
|
361
361
|
`);
|
|
362
362
|
expect(reply).toBeDefined();
|
|
363
|
-
expect(reply.
|
|
363
|
+
expect(getMessageVisibleText(reply).trim().length).toBeGreaterThan(0);
|
|
364
364
|
}, 15_000);
|
|
365
365
|
// Reconnect test lives in gateway-proxy-reconnect.e2e.test.ts.
|
|
366
366
|
// It was here before but kills the proxy mid-suite, breaking shared
|
|
@@ -382,14 +382,14 @@ describeIf('gateway-proxy e2e', () => {
|
|
|
382
382
|
"--- from: user (proxy-tester)
|
|
383
383
|
hello from gateway proxy test
|
|
384
384
|
--- from: assistant (TestBot)
|
|
385
|
-
*using deterministic-provider/deterministic-v2*
|
|
386
|
-
|
|
387
|
-
*project ⋅ main ⋅
|
|
385
|
+
> *using deterministic-provider/deterministic-v2*
|
|
386
|
+
> gateway-proxy-reply
|
|
387
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@900000000000000001>
|
|
388
388
|
--- from: user (proxy-tester)
|
|
389
389
|
follow up through proxy
|
|
390
390
|
--- from: assistant (TestBot)
|
|
391
|
-
|
|
392
|
-
*project ⋅ main ⋅
|
|
391
|
+
> gateway-proxy-reply
|
|
392
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@900000000000000001>
|
|
393
393
|
--- from: user (proxy-tester)
|
|
394
394
|
!echo proxy-shell-test
|
|
395
395
|
--- from: assistant (TestBot)
|
|
@@ -417,10 +417,10 @@ describeIf('gateway-proxy e2e', () => {
|
|
|
417
417
|
"--- from: user (proxy-tester)
|
|
418
418
|
second message through proxy
|
|
419
419
|
--- from: assistant (TestBot)
|
|
420
|
-
*using deterministic-provider/deterministic-v2*"
|
|
420
|
+
> *using deterministic-provider/deterministic-v2*"
|
|
421
421
|
`);
|
|
422
422
|
expect(reply).toBeDefined();
|
|
423
|
-
expect(reply.
|
|
423
|
+
expect(getMessageVisibleText(reply).trim().length).toBeGreaterThan(0);
|
|
424
424
|
}, 15_000);
|
|
425
425
|
test('guild-2 message does not create thread (guild isolation)', async () => {
|
|
426
426
|
await discord.channel(CHANNEL_2_ID).user(TEST_USER_ID).sendMessage({
|