AI-Repo-Commander/Docs/diagrams/file_diagrams/detector.puml

474 lines
14 KiB
Plaintext

' ===================================================================
' File: detector.puml
' Purpose: Single source of truth for module-level activity + per-method sequences.
' Module: detector.js — Observe assistant messages, settle text, extract @bridge@ blocks, enqueue.
' Edit rules: Follow the legend at bottom; preserve VIEW/METHOD anchors for automation.
' ===================================================================
' ==== VIEW: Branch Flow (detector.js) =======================================
@startuml
skinparam Shadowing false
skinparam SequenceMessageAlign center
skinparam SequenceLifeLineBorderColor #666666
skinparam SequenceLifeLineBorderThickness 1
title detector.js — Branch Flow (full module)
start
:detector;
fork
' -------- extractAllBlocks(text) --------
partition "extractAllBlocks(text)" #E7FAE3 {
:extractAllBlocks;
:regex /^\s*@bridge@...@end@/gm;
:return array of blocks;
kill
}
fork again
' -------- isAssistantMsg(el) --------
partition "isAssistantMsg(el)" #FFF6D1 {
:isAssistantMsg;
:match known selectors / descendants;
:return true/false;
kill
}
fork again
' -------- settleText(el, initial, windowMs, pollMs) --------
partition "settleText(el, initial, windowMs, pollMs)" #FFE1DB {
:settleText;
:deadline = now + windowMs;
:poll text every pollMs;\nconcat blocks; reset deadline on change;
:return last stable pick;
kill
}
fork again
' -------- Detector.constructor() --------
partition "Detector.constructor()" #DCF9EE {
:constructor;
:observer=null; processed=WeakSet;\nclusterLookahead=3; clusterWindowMs=1000;
kill
}
fork again
' -------- Detector.start() --------
partition "Detector.start()" #FFE6F0 {
:start;
:guard: if observer exists -> warn+return;
:create MutationObserver (childList, characterData, attributes);
:attach to document.body (subtree);
:optionally process existing messages (cfg.ui.processExisting);
kill
}
fork again
' -------- Detector._handle(el) --------
partition "Detector._handle(el)" #E6F3FF {
:_handle;
:skip if already processed;\nadd to processed;
:debounce (cfg.execution.debounceDelay);
:stable = settleText(...);
:blocks = extractAllBlocks(stable);
:if blocks empty -> unmark + return;
:cap by cfg.queue.maxPerMessage;\nfor each: _enqueueOne(el, text, idx);
:schedule _clusterRescan(el) after window;
kill
}
fork again
' -------- Detector._enqueueOne(el, text, idx) --------
partition "Detector._enqueueOne(el, text, idx)" #F0E6FA {
:_enqueueOne;
:if history.isProcessed -> add run-again button + return;
:history.markProcessed;
:AI_REPO_QUEUE.push(async task { parse -> validate -> execute | catch -> run-again });
kill
}
fork again
' -------- Detector._addRunAgain(el, text, idx) --------
partition "Detector._addRunAgain(el, text, idx)" #E7FAF7 {
:_addRunAgain;
:create button "Run Again #n"; onClick -> _enqueueOne(...);
:append to message el;
kill
}
fork again
' -------- Detector._clusterRescan(anchor) --------
partition "Detector._clusterRescan(anchor)" #FFF2E7 {
:_clusterRescan;
:walk nextElementSibling up to clusterLookahead;
:if assistant & not processed -> _handle(cur);
kill
}
end fork
@enduml
' ==== METHOD: extractAllBlocks(text) ========================================
@startuml
title detector:extractAllBlocks(text): \n Return all complete @bridge@...@end@ fenced blocks
participant "Caller" as CL
participant "extractAllBlocks(text)" as EXT
activate CL
CL -> EXT : initial request (text)
activate EXT
EXT -> EXT : regex /^\s*@bridge@\\n([\\s\\S]*?)\\n@end@/gm
EXT --> CL : blocks[]
deactivate EXT
deactivate CL
@enduml
' ==== METHOD: isAssistantMsg(el) ============================================
@startuml
title detector:isAssistantMsg(el): \n Identify assistant/authored content nodes in the DOM
participant "Caller" as CL
participant "isAssistantMsg(el)" as IAM
activate CL
CL -> IAM : initial request (el)
activate IAM
IAM -> IAM : check selectors\n[data-message-author-role="assistant"]\n.chat-message:not([data-message-author-role="user"])\n.message-content
IAM --> CL : true/false
deactivate IAM
deactivate CL
@enduml
' ==== METHOD: settleText(el, initial, windowMs, pollMs) =====================
@startuml
title detector:settleText(el, initial, windowMs, pollMs): \n Wait for streaming text to stabilize
participant "Caller" as CL
participant "settleText(...)" as ST
participant "Timer" as TMR
participant "extractAllBlocks(text)" as EXT
activate CL
CL -> ST : initial request (el, initial, windowMs, pollMs)
activate ST
ST -> ST : deadline = now + windowMs; last = initial
loop poll until deadline
ST -> TMR : setTimeout(pollMs)
TMR --> ST : wake
ST -> ST : fresh = el.textContent || ''
ST -> EXT : extractAllBlocks(fresh)
EXT --> ST : blocks[]
ST -> ST : pick = blocks.join('\\n')
alt pick == last AND pick not empty
ST -> ST : continue (keep waiting within window)
else pick changed AND not empty
ST -> ST : last = pick; deadline = now + windowMs
end
end
ST --> CL : last
deactivate ST
deactivate CL
@enduml
' ==== METHOD: Detector.constructor() ========================================
@startuml
title detector:constructor(): \n Initialize observer, processed set, cluster knobs
participant "Caller" as CL
participant "constructor()" as CTOR
activate CL
CL -> CTOR : new Detector()
activate CTOR
CTOR -> CTOR : observer=null; processed=WeakSet()\nclusterLookahead=3; clusterWindowMs=1000
CTOR --> CL : instance
deactivate CTOR
deactivate CL
@enduml
' ==== METHOD: Detector.start() ==============================================
@startuml
title detector:start(): \n Attach MutationObserver; optionally process existing messages
participant "Caller" as CL
participant "start()" as ST
participant "Logger" as LOG
participant "Config" as CFG
participant "MutationObserver" as MO
participant "DOM" as DOM
activate CL
CL -> ST : initial request
activate ST
ST -> ST : if observer exists -> LOG.warn + return
ST -> LOG : info("Starting advanced detector…")
LOG --> ST : ok
ST -> MO : new MutationObserver(callback)
MO --> ST : observer
ST -> DOM : observe(document.body, { childList, subtree, characterData, attributes })
DOM --> ST : attached
ST -> CFG : get('ui.processExisting')
CFG --> ST : true/false
alt process existing
ST -> DOM : querySelectorAll('[data-message-author-role], .chat-message, .message-content')
DOM --> ST : NodeList
ST -> ST : for each el: if isAssistantMsg(el) -> _handle(el)
else skip
ST -> LOG : trace("processExisting disabled")
end
ST -> LOG : info("Detector started and monitoring")
ST --> CL : started
deactivate ST
deactivate CL
@enduml
' ==== METHOD: Detector._handle(el) ==========================================
@startuml
title detector:_handle(el): \n Debounce, settle text, extract blocks, enqueue, schedule cluster rescan
participant "Caller" as CL
participant "_handle(el)" as HDL
participant "Logger" as LOG
participant "Config" as CFG
participant "settleText(...)" as ST
participant "extractAllBlocks(text)" as EXT
participant "_enqueueOne(el, text, idx)" as ENQ
participant "_clusterRescan(anchor)" as CRS
participant "Timer" as TMR
activate CL
CL -> HDL : initial request (el)
activate HDL
HDL -> HDL : if processed.has(el) -> LOG.trace + return
HDL -> HDL : processed.add(el)
HDL -> LOG : verbose("Processing new assistant message")
LOG --> HDL : ok
HDL -> CFG : get('execution.debounceDelay')
CFG --> HDL : debounce (ms)
alt debounce > 0
HDL -> LOG : trace(`Debouncing for ${debounce}ms`)
HDL -> TMR : setTimeout(debounce)
TMR --> HDL : wake
end
HDL -> HDL : baseText = el.textContent || ''
HDL -> LOG : trace("Starting text settle check", { textLength })
HDL -> CFG : get('execution.settleCheckMs') / get('execution.settlePollMs')
CFG --> HDL : settleMs / pollMs
HDL -> ST : settleText(el, baseText, settleMs, pollMs)
ST --> HDL : stable
HDL -> EXT : extractAllBlocks(stable)
EXT --> HDL : blocks[]
HDL -> LOG : verbose(`Found ${blocks.length} block(s)`)
alt blocks.length == 0
HDL -> LOG : trace("No blocks; removing from processed")
HDL -> HDL : processed.delete(el)
HDL --> CL : done
deactivate HDL
deactivate CL
return
end
HDL -> CFG : get('queue.maxPerMessage')
CFG --> HDL : maxPerMsg
alt blocks.length > maxPerMsg
HDL -> LOG : warn(`limiting to ${maxPerMsg}`)
end
HDL -> HDL : for (idx, cmdText) in blocks.slice(0, maxPerMsg)
HDL -> ENQ : _enqueueOne(el, cmdText, idx)
ENQ --> HDL : queued
HDL -> LOG : trace(`Scheduling cluster rescan`)
HDL -> TMR : setTimeout(clusterWindowMs)
TMR --> HDL : wake
HDL -> CRS : _clusterRescan(el)
CRS --> HDL : done
HDL --> CL : done
deactivate HDL
deactivate CL
@enduml
' ==== METHOD: Detector._enqueueOne(el, commandText, idx) ====================
@startuml
title detector:_enqueueOne(el, commandText, idx): \n History gate; queue async task (parse → validate → execute)
participant "Caller" as CL
participant "_enqueueOne(...)" as ENQ
participant "Logger" as LOG
participant "History" as HST
participant "Queue" as QUE
participant "Parser" as PAR
participant "Executor" as EXE
participant "_addRunAgain(el, text, idx)" as RAG
activate CL
CL -> ENQ : initial request (el, text, idx)
activate ENQ
ENQ -> HST : isProcessed(el, idx)
HST --> ENQ : true/false
alt already processed
ENQ -> LOG : verbose("already processed, adding retry button")
ENQ -> RAG : _addRunAgain(el, text, idx)
RAG --> ENQ : button added
ENQ --> CL : done
deactivate ENQ
deactivate CL
return
end
ENQ -> LOG : trace("mark processed")
ENQ -> HST : markProcessed(el, idx)
HST --> ENQ : ok
ENQ -> LOG : verbose("pushing command to queue")
ENQ -> QUE : push(async task)
QUE --> ENQ : ok
' --- async task (conceptual) ---
ENQ -> PAR : parse(text)
PAR --> ENQ : parsed (action, repo, path, ...)
ENQ -> PAR : validate(parsed)
PAR --> ENQ : { isValid, errors?, example? }
alt example is true
ENQ -> LOG : info("Example command skipped")
else invalid
ENQ -> LOG : warn("Validation failed", errors)
ENQ -> RAG : _addRunAgain(el, text, idx)
RAG --> ENQ : added
else valid
ENQ -> LOG : verbose(`Executing: ${parsed.action}`)
ENQ -> EXE : execute(parsed, el, `[${idx+1}] ${action}`)
EXE --> ENQ : result | error
end
ENQ --> CL : done
deactivate ENQ
deactivate CL
@enduml
' ==== METHOD: Detector._addRunAgain(el, commandText, idx) ===================
@startuml
title detector:_addRunAgain(el, commandText, idx): \n Add a “Run Again #n” button that re-enqueues the command
participant "Caller" as CL
participant "_addRunAgain(...)" as RAG
participant "DOM" as DOM
participant "_enqueueOne(...)" as ENQ
activate CL
CL -> RAG : initial request (el, text, idx)
activate RAG
RAG -> DOM : createElement('button')
DOM --> RAG : btn
RAG -> DOM : set text, style, click handler -> ENQ(...)
DOM --> RAG : configured
RAG -> DOM : append btn to el
DOM --> RAG : appended
RAG --> CL : done
deactivate RAG
deactivate CL
@enduml
' ==== METHOD: Detector._clusterRescan(anchor) ===============================
@startuml
title detector:_clusterRescan(anchor): \n Scan next siblings for assistant messages and handle them
participant "Caller" as CL
participant "_clusterRescan(anchor)" as CRS
participant "DOM" as DOM
participant "isAssistantMsg(el)" as IAM
participant "_handle(el)" as HDL
activate CL
CL -> CRS : initial request (anchor)
activate CRS
CRS -> DOM : cur = anchor.nextElementSibling
loop up to clusterLookahead
CRS -> IAM : isAssistantMsg(cur)
IAM --> CRS : true/false
alt is assistant
CRS -> CRS : if not processed -> _handle(cur)
CRS -> HDL : _handle(cur)
HDL --> CRS : done
else not assistant
CRS -> CRS : break
end
CRS -> DOM : cur = cur.nextElementSibling
end
CRS --> CL : done
deactivate CRS
deactivate CL
@enduml
' ==== LEGEND ================================================================
@startuml
legend bottom
== detector UML Style Guide (for future edits) ==
• Scope: One .puml per module. Keep two views:
(1) Activity "Branch Flow" for the whole module (partitions + soft colors),
(2) Per-function Sequence diagrams for each exported or significant internal method.
• Sequence conventions:
1) First participant is the external caller (use "Caller" or a concrete origin like "Page").
2) Do NOT add a module lifeline; the module name appears in the title only.
3) Include every directly-called method or subsystem as a participant
(e.g., "extractAllBlocks()", "isAssistantMsg()", "settleText()", "Parser", "Executor", "Queue", "History", "Config", "Logger", "Timer", "DOM").
4) Prefer simple messages; Use --> for returns; -> for calls.
5) Use activate/deactivate as you see fit for clarity (no strict rule).
6) Use alt blocks only when branches meaningfully change the message flow.
• Activity view conventions:
A) Start with module node then fork partitions for each function/method.
B) One partition per function; soft background color; terminate branches with 'kill'.
C) Keep wording aligned with code (e.g., "deepClone(DEFAULT_CONFIG)", "mergeConfigs(...)").
• Color palette (soft pastels)
• Keep this legend at the end of the file to standardize edits.
UML_Example
------------------------------------------
title moduleName:methodName(args): \n Detailed description of what this method does
participant "Caller" as CL
participant "methodName()" as M
' Add collaborators as needed:
' participant "Dependency" as DEP
' participant "AnotherMethod()" as AM
' participant "DOM" as DOM
' participant "Timer" as TMR
activate CL
CL -> M : initial request (args)
activate M
' -- inner flow (keep alt blocks only if they clarify) --
' M -> DEP : call something
' DEP --> M : result
' alt branch condition
' M -> AM : call another
' AM --> M : result
' else other branch
' M -> DOM : do something with the DOM
' DOM --> M : ok
' end
M --> CL : return value
deactivate M
deactivate CL
------------------------------------------
endlegend
@enduml