kimaki 0.4.77 → 0.4.78
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/cli.js +27 -0
- package/dist/commands/diff.js +20 -85
- package/dist/commands/screenshare.js +295 -0
- package/dist/critique-utils.js +95 -0
- package/dist/diff-patch-plugin.js +314 -0
- package/dist/discord-bot.js +1 -1
- package/dist/interaction-handler.js +10 -0
- package/dist/message-formatting.js +3 -62
- package/dist/onboarding-tutorial-plugin.js +1 -1
- package/dist/opencode-plugin.js +4 -4
- package/dist/patch-text-parser.js +97 -0
- package/dist/session-handler/thread-session-runtime.js +1 -1
- package/dist/websockify.js +69 -0
- package/package.json +7 -5
- package/skills/event-sourcing-state/SKILL.md +188 -34
- package/skills/playwriter/SKILL.md +1 -1
- package/src/cli.ts +35 -0
- package/src/commands/diff.ts +25 -99
- package/src/commands/screenshare.ts +354 -0
- package/src/critique-utils.ts +139 -0
- package/src/discord-bot.ts +1 -1
- package/src/interaction-handler.ts +15 -0
- package/src/message-formatting.ts +3 -68
- package/src/onboarding-tutorial-plugin.ts +1 -1
- package/src/opencode-plugin.ts +5 -4
- package/src/patch-text-parser.ts +107 -0
- package/src/session-handler/thread-session-runtime.ts +2 -1
- package/src/websockify.ts +101 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Shared apply_patch text parsing utilities.
|
|
2
|
+
// Used by diff-patch-plugin.ts (file path extraction for snapshots) and
|
|
3
|
+
// message-formatting.ts (per-file addition/deletion counts for Discord display).
|
|
4
|
+
//
|
|
5
|
+
// The apply_patch tool uses three path header formats:
|
|
6
|
+
// *** Add File: path — new file
|
|
7
|
+
// *** Update File: path — existing file edit
|
|
8
|
+
// *** Delete File: path — file removal
|
|
9
|
+
// *** Move to: path — rename destination
|
|
10
|
+
// --- a/path / +++ b/path — unified diff headers (fallback)
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Extract all file paths referenced in a patchText string.
|
|
14
|
+
* Handles custom apply_patch headers, move targets, and unified diff headers.
|
|
15
|
+
* Returns deduplicated paths.
|
|
16
|
+
*/
|
|
17
|
+
export function extractPatchFilePaths(patchText: string): string[] {
|
|
18
|
+
const custom = [
|
|
19
|
+
...patchText.matchAll(
|
|
20
|
+
/^\*\*\* (?:Add|Update|Delete) File:\s+(.+)$/gm,
|
|
21
|
+
),
|
|
22
|
+
].map((m) => {
|
|
23
|
+
return (m[1] ?? '').trim()
|
|
24
|
+
})
|
|
25
|
+
const moved = [
|
|
26
|
+
...patchText.matchAll(/^\*\*\* Move to:\s+(.+)$/gm),
|
|
27
|
+
].map((m) => {
|
|
28
|
+
return (m[1] ?? '').trim()
|
|
29
|
+
})
|
|
30
|
+
const unified = [
|
|
31
|
+
...patchText.matchAll(/^(?:---|\+\+\+) [ab]\/(.+)$/gm),
|
|
32
|
+
].map((m) => {
|
|
33
|
+
return (m[1] ?? '').trim()
|
|
34
|
+
})
|
|
35
|
+
const all = [...custom, ...moved, ...unified].filter(Boolean)
|
|
36
|
+
return all.filter((v, i, a) => {
|
|
37
|
+
return a.indexOf(v) === i
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Parse a patchText string and count additions/deletions per file.
|
|
43
|
+
* Patch format uses `*** Add File:`, `*** Update File:`, `*** Delete File:` headers,
|
|
44
|
+
* with diff lines prefixed by `+` (addition) or `-` (deletion) inside `@@` hunks.
|
|
45
|
+
*/
|
|
46
|
+
export function parsePatchFileCounts(
|
|
47
|
+
patchText: string,
|
|
48
|
+
): Map<string, { additions: number; deletions: number }> {
|
|
49
|
+
const counts = new Map<string, { additions: number; deletions: number }>()
|
|
50
|
+
const lines = patchText.split('\n')
|
|
51
|
+
let currentFile = ''
|
|
52
|
+
let currentType = ''
|
|
53
|
+
let inHunk = false
|
|
54
|
+
|
|
55
|
+
for (const line of lines) {
|
|
56
|
+
const addMatch = line.match(/^\*\*\* Add File:\s*(.+)/)
|
|
57
|
+
const updateMatch = line.match(/^\*\*\* Update File:\s*(.+)/)
|
|
58
|
+
const deleteMatch = line.match(/^\*\*\* Delete File:\s*(.+)/)
|
|
59
|
+
|
|
60
|
+
if (addMatch || updateMatch || deleteMatch) {
|
|
61
|
+
const match = addMatch || updateMatch || deleteMatch
|
|
62
|
+
currentFile = (match?.[1] ?? '').trim()
|
|
63
|
+
currentType = addMatch ? 'add' : updateMatch ? 'update' : 'delete'
|
|
64
|
+
counts.set(currentFile, { additions: 0, deletions: 0 })
|
|
65
|
+
inHunk = false
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (line.startsWith('@@')) {
|
|
70
|
+
inHunk = true
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (line.startsWith('*** ')) {
|
|
75
|
+
inHunk = false
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!currentFile) {
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const entry = counts.get(currentFile)
|
|
84
|
+
if (!entry) {
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (currentType === 'add') {
|
|
89
|
+
// all content lines in Add File are additions
|
|
90
|
+
if (line.length > 0 && !line.startsWith('*** ')) {
|
|
91
|
+
entry.additions++
|
|
92
|
+
}
|
|
93
|
+
} else if (currentType === 'delete') {
|
|
94
|
+
// all content lines in Delete File are deletions
|
|
95
|
+
if (line.length > 0 && !line.startsWith('*** ')) {
|
|
96
|
+
entry.deletions++
|
|
97
|
+
}
|
|
98
|
+
} else if (inHunk) {
|
|
99
|
+
if (line.startsWith('+')) {
|
|
100
|
+
entry.additions++
|
|
101
|
+
} else if (line.startsWith('-')) {
|
|
102
|
+
entry.deletions++
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return counts
|
|
107
|
+
}
|
|
@@ -123,6 +123,7 @@ import {
|
|
|
123
123
|
matchThinkingValue,
|
|
124
124
|
} from '../thinking-utils.js'
|
|
125
125
|
import { execAsync } from '../worktrees.js'
|
|
126
|
+
|
|
126
127
|
import { notifyError } from '../sentry.js'
|
|
127
128
|
import { createDebouncedProcessFlush } from '../debounced-process-flush.js'
|
|
128
129
|
import { cancelHtmlActionsForThread } from '../html-actions.js'
|
|
@@ -3534,7 +3535,7 @@ export class ThreadSessionRuntime {
|
|
|
3534
3535
|
|
|
3535
3536
|
const client = getOpencodeClient(this.projectDirectory)
|
|
3536
3537
|
|
|
3537
|
-
// Run git branch
|
|
3538
|
+
// Run git branch and token fetch in parallel (fast, no external CLI)
|
|
3538
3539
|
const [branchResult, contextResult] = await Promise.all([
|
|
3539
3540
|
errore.tryAsync(() => {
|
|
3540
3541
|
return execAsync('git symbolic-ref --short HEAD', {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// In-process WebSocket-to-TCP bridge (websockify replacement).
|
|
2
|
+
// Accepts WebSocket connections and pipes raw bytes to/from a TCP target.
|
|
3
|
+
// Used by /screenshare to bridge noVNC (WebSocket) to a VNC server (TCP).
|
|
4
|
+
// Supports the 'binary' subprotocol required by noVNC.
|
|
5
|
+
|
|
6
|
+
import { WebSocketServer, WebSocket } from 'ws'
|
|
7
|
+
import net from 'node:net'
|
|
8
|
+
import { createLogger } from './logger.js'
|
|
9
|
+
|
|
10
|
+
const logger = createLogger('SCREEN')
|
|
11
|
+
|
|
12
|
+
type WebsockifyOptions = {
|
|
13
|
+
/** Port for the WebSocket server (0 = auto-assign) */
|
|
14
|
+
wsPort: number
|
|
15
|
+
/** TCP target host */
|
|
16
|
+
tcpHost: string
|
|
17
|
+
/** TCP target port */
|
|
18
|
+
tcpPort: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type WebsockifyInstance = {
|
|
22
|
+
wss: WebSocketServer
|
|
23
|
+
/** Resolved port (useful when wsPort=0) */
|
|
24
|
+
port: number
|
|
25
|
+
close: () => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function startWebsockify({
|
|
29
|
+
wsPort,
|
|
30
|
+
tcpHost,
|
|
31
|
+
tcpPort,
|
|
32
|
+
}: WebsockifyOptions): Promise<WebsockifyInstance> {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const wss = new WebSocketServer({
|
|
35
|
+
port: wsPort,
|
|
36
|
+
// noVNC negotiates the 'binary' subprotocol
|
|
37
|
+
handleProtocols: (protocols) => {
|
|
38
|
+
if (protocols.has('binary')) {
|
|
39
|
+
return 'binary'
|
|
40
|
+
}
|
|
41
|
+
return false
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
wss.on('listening', () => {
|
|
46
|
+
const addr = wss.address()
|
|
47
|
+
const port = typeof addr === 'object' && addr ? addr.port : wsPort
|
|
48
|
+
logger.log(`Websockify listening on port ${port} → ${tcpHost}:${tcpPort}`)
|
|
49
|
+
resolve({
|
|
50
|
+
wss,
|
|
51
|
+
port,
|
|
52
|
+
close: () => {
|
|
53
|
+
for (const client of wss.clients) {
|
|
54
|
+
client.close()
|
|
55
|
+
}
|
|
56
|
+
wss.close()
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
wss.on('error', (err) => {
|
|
62
|
+
reject(new Error('Websockify failed to start', { cause: err }))
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
wss.on('connection', (ws) => {
|
|
66
|
+
const tcp = net.createConnection(tcpPort, tcpHost, () => {
|
|
67
|
+
logger.log(`TCP connection established to ${tcpHost}:${tcpPort}`)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
tcp.on('data', (data) => {
|
|
71
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
72
|
+
ws.send(data)
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
ws.on('message', (data: Buffer) => {
|
|
77
|
+
if (!tcp.destroyed) {
|
|
78
|
+
tcp.write(data)
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
ws.on('close', () => {
|
|
83
|
+
tcp.destroy()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
ws.on('error', (err) => {
|
|
87
|
+
logger.error('WebSocket error:', err)
|
|
88
|
+
tcp.destroy()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
tcp.on('close', () => {
|
|
92
|
+
ws.close()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
tcp.on('error', (err) => {
|
|
96
|
+
logger.error('TCP connection error:', err)
|
|
97
|
+
ws.close()
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
}
|