gitdone-agent 0.6.2 → 0.6.3
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/index.js +48 -9
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -27,7 +27,7 @@ import { randomUUID } from 'node:crypto'
|
|
|
27
27
|
// Reported to the server on every sync so the web UI can flag outdated agents.
|
|
28
28
|
// Keep in lockstep with packages/agent/package.json "version" AND
|
|
29
29
|
// src/lib/agentVersion.ts LATEST_AGENT_VERSION.
|
|
30
|
-
const AGENT_VERSION = '0.6.
|
|
30
|
+
const AGENT_VERSION = '0.6.3'
|
|
31
31
|
|
|
32
32
|
const AGENT_DIR = join(homedir(), '.gitdone-agent')
|
|
33
33
|
const CONFIG_PATH = join(AGENT_DIR, 'config.json')
|
|
@@ -521,19 +521,24 @@ async function postRunEvents(cfg, runId, events, status, result) {
|
|
|
521
521
|
|
|
522
522
|
// Post transcript lines (and optional turn status / Claude session id) for an
|
|
523
523
|
// interactive AiSession chat turn.
|
|
524
|
-
async function postSessionEvents(cfg, sessionId, events, status, claudeSessionId) {
|
|
524
|
+
async function postSessionEvents(cfg, sessionId, events, status, claudeSessionId, streamingText) {
|
|
525
525
|
await api(cfg, '/api/v1/agent/ai-session/events', {
|
|
526
526
|
sessionId,
|
|
527
527
|
events,
|
|
528
528
|
...(status ? { status } : {}),
|
|
529
529
|
...(claudeSessionId ? { claudeSessionId } : {}),
|
|
530
|
+
// Explicit '' clears the live preview; `undefined` leaves it untouched.
|
|
531
|
+
...(streamingText !== undefined ? { streamingText } : {}),
|
|
530
532
|
}).catch((e) => log(`✗ ai-session events post failed: ${e.message}`))
|
|
531
533
|
}
|
|
532
534
|
|
|
533
535
|
// Turn one stream-json line from `claude -p` into console events. Phase 1 shows
|
|
534
536
|
// what the model writes (TEXT) and which tools it calls (TOOL_CALL); raw tool
|
|
535
537
|
// results (Bash stdout etc.) come in Phase 2 via a PostToolUse hook.
|
|
536
|
-
|
|
538
|
+
// With `--include-partial-messages`, Claude also emits `stream_event` lines
|
|
539
|
+
// carrying incremental text deltas — onDelta gets those so chat sessions can
|
|
540
|
+
// show the reply being written live (gd-302).
|
|
541
|
+
function parseStreamLine(line, push, onInit, onDelta) {
|
|
537
542
|
let ev
|
|
538
543
|
try { ev = JSON.parse(line) } catch { return }
|
|
539
544
|
if (ev.type === 'system') {
|
|
@@ -544,6 +549,16 @@ function parseStreamLine(line, push, onInit) {
|
|
|
544
549
|
}
|
|
545
550
|
return
|
|
546
551
|
}
|
|
552
|
+
// Partial-message stream: only the model's visible text deltas feed the live
|
|
553
|
+
// preview. Tool-input JSON deltas and thinking deltas are ignored here — the
|
|
554
|
+
// full block still arrives as a normal `assistant` event below.
|
|
555
|
+
if (ev.type === 'stream_event' && typeof onDelta === 'function') {
|
|
556
|
+
const e = ev.event
|
|
557
|
+
if (e?.type === 'content_block_delta' && e.delta?.type === 'text_delta' && e.delta.text) {
|
|
558
|
+
onDelta(e.delta.text)
|
|
559
|
+
}
|
|
560
|
+
return
|
|
561
|
+
}
|
|
547
562
|
if (ev.type === 'assistant' && ev.message?.content) {
|
|
548
563
|
for (const block of ev.message.content) {
|
|
549
564
|
if (block.type === 'text' && block.text?.trim()) {
|
|
@@ -822,6 +837,9 @@ async function runAiChat(cfg, cmd, repoPath) {
|
|
|
822
837
|
'-p',
|
|
823
838
|
'--output-format', 'stream-json',
|
|
824
839
|
'--verbose',
|
|
840
|
+
// Stream the reply token-by-token so the console can show it being written
|
|
841
|
+
// live, not only once each block is complete (gd-302).
|
|
842
|
+
'--include-partial-messages',
|
|
825
843
|
'--permission-mode', 'acceptEdits',
|
|
826
844
|
'--allowedTools', 'Read,Edit,Write,Bash,mcp__gitdone__*',
|
|
827
845
|
// Block git commit/push unless this repo opted in (per-repo aiAutoCommit).
|
|
@@ -837,16 +855,35 @@ async function runAiChat(cfg, cmd, repoPath) {
|
|
|
837
855
|
let pending = []
|
|
838
856
|
let capturedSession = null // Claude's session id, sent (idempotently) for --resume
|
|
839
857
|
let flushing = false
|
|
858
|
+
let liveText = '' // in-progress text of the current assistant block (live preview)
|
|
859
|
+
let sentLive = '' // last streamingText we posted — so we only push on change
|
|
860
|
+
let lastPostAt = 0 // Date.now() of the last post, for the keep-alive heartbeat
|
|
840
861
|
const flush = async () => {
|
|
841
|
-
if (flushing
|
|
862
|
+
if (flushing) return
|
|
863
|
+
const hasEvents = pending.length > 0
|
|
864
|
+
const liveChanged = liveText !== sentLive
|
|
865
|
+
// Heartbeat: with nothing new for a while, still post (bumps lastActivityAt)
|
|
866
|
+
// so the console shows honest "still working" and can spot a real stall.
|
|
867
|
+
const heartbeat = !hasEvents && !liveChanged && Date.now() - lastPostAt > 2500
|
|
868
|
+
if (!hasEvents && !liveChanged && !heartbeat) return
|
|
842
869
|
flushing = true
|
|
843
870
|
const batch = pending.map((e) => ({ role: roleFor(e.kind), text: e.text })); pending = []
|
|
844
|
-
|
|
871
|
+
const streamingText = liveChanged ? liveText : undefined
|
|
872
|
+
sentLive = liveText
|
|
873
|
+
await postSessionEvents(cfg, sessionId, batch, 'running', capturedSession || undefined, streamingText)
|
|
874
|
+
lastPostAt = Date.now()
|
|
845
875
|
flushing = false
|
|
846
876
|
}
|
|
847
|
-
const push = (kind, text) => {
|
|
877
|
+
const push = (kind, text) => {
|
|
878
|
+
if (text == null || String(text) === '') return
|
|
879
|
+
// A completed text block becomes a real event — drop its live preview so the
|
|
880
|
+
// finalised bubble and the cleared preview swap in on the same flush.
|
|
881
|
+
if (kind === 'TEXT') liveText = ''
|
|
882
|
+
pending.push({ kind, text: String(text) })
|
|
883
|
+
}
|
|
848
884
|
const onInit = (sid) => { if (sid && !claudeSessionId) capturedSession = sid }
|
|
849
|
-
const
|
|
885
|
+
const onDelta = (chunk) => { liveText += chunk }
|
|
886
|
+
const timer = setInterval(flush, 500)
|
|
850
887
|
|
|
851
888
|
// Fetch any attached images locally and fold their paths into the prompt so
|
|
852
889
|
// Claude reads them with its Read tool (URLs on stdin don't render).
|
|
@@ -888,7 +925,7 @@ async function runAiChat(cfg, cmd, repoPath) {
|
|
|
888
925
|
while ((nl = buf.indexOf('\n')) >= 0) {
|
|
889
926
|
const line = buf.slice(0, nl).trim()
|
|
890
927
|
buf = buf.slice(nl + 1)
|
|
891
|
-
if (line) parseStreamLine(line, push, onInit)
|
|
928
|
+
if (line) parseStreamLine(line, push, onInit, onDelta)
|
|
892
929
|
}
|
|
893
930
|
})
|
|
894
931
|
child.stderr.on('data', (d) => { const s = d.toString().trim(); if (s) push('SYSTEM', s) })
|
|
@@ -896,7 +933,8 @@ async function runAiChat(cfg, cmd, repoPath) {
|
|
|
896
933
|
child.on('close', async (code) => {
|
|
897
934
|
clearInterval(timer)
|
|
898
935
|
if (imgDir) { try { rmSync(imgDir, { recursive: true, force: true }) } catch { /* best-effort */ } }
|
|
899
|
-
if (buf.trim()) parseStreamLine(buf.trim(), push, onInit)
|
|
936
|
+
if (buf.trim()) parseStreamLine(buf.trim(), push, onInit, onDelta)
|
|
937
|
+
liveText = '' // turn is over — drop any lingering live preview
|
|
900
938
|
await flush()
|
|
901
939
|
const ok = code === 0
|
|
902
940
|
await postSessionEvents(
|
|
@@ -904,6 +942,7 @@ async function runAiChat(cfg, cmd, repoPath) {
|
|
|
904
942
|
ok ? [] : [{ role: 'SYSTEM', text: `✗ Ходът приключи с код ${code}.` }],
|
|
905
943
|
ok ? 'idle' : 'error',
|
|
906
944
|
capturedSession || undefined,
|
|
945
|
+
'', // clear the live preview on the terminal post
|
|
907
946
|
)
|
|
908
947
|
reportCommandResult(cfg, cmd.id, ok ? 'done' : 'error', `exit ${code}`)
|
|
909
948
|
log(`■ ai_chat ${sessionId} приключи (code ${code})`)
|