435 lines
14 KiB
Plaintext
435 lines
14 KiB
Plaintext
' ===================================================================
|
|
' File: command-executor.puml
|
|
' Purpose: Single source of truth for module-level activity + per-method sequences.
|
|
' Module: command-executor.js — Execute validated repo commands via bridge API; retries, timeouts, mock mode.
|
|
' Edit rules: Follow the legend at bottom; preserve VIEW/METHOD anchors for automation.
|
|
' ===================================================================
|
|
|
|
' Neutral defaults — typography/layout only (keeps partition colors intact)
|
|
skinparam Shadowing false
|
|
skinparam SequenceMessageAlign center
|
|
skinparam SequenceLifeLineBorderColor #666666
|
|
skinparam SequenceLifeLineBorderThickness 1
|
|
|
|
' ==== VIEW: Branch Flow (command-executor.js) ==============================
|
|
@startuml
|
|
title command-executor.js — Branch Flow (full module)
|
|
|
|
start
|
|
:command-executor;
|
|
|
|
fork
|
|
' -------- execute(command, el, label) --------
|
|
partition "execute(command, el, label)" #E7FAE3 {
|
|
:execute;
|
|
:log.command(action,'executing');\nautogen commit_message (for file ops);
|
|
:if api.disabled -> delay + _success(mock);
|
|
:else -> res=_api(command) -> _success(res);
|
|
:catch -> _error(err);
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- _api(command, attempt=0) --------
|
|
partition "_api(command, attempt=0)" #FFF6D1 {
|
|
:_api;
|
|
:read cfg (maxRetries, timeout, bridgeKey);\nPOST JSON via GM_xmlhttpRequest;
|
|
:onload 2xx -> resolve;\nelse -> reject(Error status);
|
|
:onerror -> retry with backoff or fail;
|
|
:ontimeout -> reject(Error timeout);
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- _getBridgeKey() --------
|
|
partition "_getBridgeKey()" #FFE1DB {
|
|
:_getBridgeKey;
|
|
:key = cfg.get('api.bridgeKey');\nif missing -> prompt;\noptional persist via cfg.set;
|
|
:return key or throw if empty;
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- _success(response, command, el, isMock, label) --------
|
|
partition "_success(response, command, el, isMock, label)" #DCF9EE {
|
|
:_success;
|
|
:parse JSON responseText; _status(el, type);\nbranch to _handleGetFile/_handleListFiles;
|
|
:return { success:true, data, isMock };
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- _error(error, command, el, label) --------
|
|
partition "_error(error, command, el, label)" #FFE6F0 {
|
|
:_error;
|
|
:_status(el, 'ERROR', details);\nreturn { success:false, error };
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- _status(el, type, data) --------
|
|
partition "_status(el, type, data)" #E6F3FF {
|
|
:_status;
|
|
:create styled div;\nleft border color via _color(type);\nappend to el;
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- _handleGetFile(data, label) --------
|
|
partition "_handleGetFile(data, label)" #F0E6FA {
|
|
:_handleGetFile;
|
|
:pull content from various shapes;\npush to window.AI_REPO_RESPONSES;
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- _handleListFiles(data, label) --------
|
|
partition "_handleListFiles(data, label)" #E7FAF7 {
|
|
:_handleListFiles;
|
|
:files[] -> text fenced listing;\npush to window.AI_REPO_RESPONSES;
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- delay(ms) --------
|
|
partition "delay(ms)" #FFF2E7 {
|
|
:delay;
|
|
:Promise(resolve after setTimeout(ms));
|
|
kill
|
|
}
|
|
end fork
|
|
@enduml
|
|
|
|
' ==== METHOD: execute(command, sourceElement, label) ========================
|
|
@startuml
|
|
title command-executor:execute(command, el, label): \n Build request, call API or mock, normalize and render result
|
|
|
|
participant "Caller" as CL
|
|
participant "execute()" as EXE
|
|
participant "delay(ms)" as DLY
|
|
participant "_api(command, attempt)" as API
|
|
participant "_success(...)" as OK
|
|
participant "_error(...)" as ERR
|
|
participant "Config" as CFG
|
|
participant "Logger" as LOG
|
|
|
|
activate CL
|
|
CL -> EXE : initial request (command, el, label)
|
|
activate EXE
|
|
|
|
EXE -> LOG : command(action,'executing', {repo,path,label})
|
|
LOG --> EXE : ok
|
|
|
|
' Auto-commit message for file ops
|
|
EXE -> EXE : if action in {update_file, create_file} and !commit_message\n commit_message = "AI Repo Commander: ... <ISO>"
|
|
|
|
' Mock path if API disabled
|
|
EXE -> CFG : get('api.enabled')
|
|
CFG --> EXE : true/false
|
|
alt api.enabled == false
|
|
EXE -> LOG : warn("API disabled, using mock")
|
|
EXE -> DLY : delay(300)
|
|
DLY --> EXE : done
|
|
EXE -> OK : _success({status:200,responseText:'{\"success\":true,...}'}, command, el, true, label)
|
|
OK --> EXE : result
|
|
EXE -> LOG : command(action,'complete', {mock:true})
|
|
EXE --> CL : result
|
|
else real API call
|
|
EXE -> LOG : verbose("Making API request", {url,label})
|
|
EXE -> API : _api(command)
|
|
API --> EXE : res {status, responseText}
|
|
EXE -> LOG : verbose("API request succeeded", {status})
|
|
EXE -> OK : _success(res, command, el, false, label)
|
|
OK --> EXE : result
|
|
EXE -> LOG : command(action,'complete', {repo,path})
|
|
EXE --> CL : result
|
|
|
|
|
|
' error handling
|
|
else error thrown
|
|
EXE -> LOG : command(action,'error', {error})
|
|
EXE -> LOG : error("Command execution failed", {action,error})
|
|
EXE -> ERR : _error(err, command, el, label)
|
|
ERR --> EXE : error result
|
|
EXE --> CL : error result
|
|
end
|
|
|
|
deactivate EXE
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: _api(command, attempt=0) =====================================
|
|
@startuml
|
|
title command-executor:_api(command, attempt=0): \n POST JSON via GM_xmlhttpRequest with retries and timeout
|
|
|
|
participant "Caller" as CL
|
|
participant "_api(command, attempt)" as API
|
|
participant "_getBridgeKey()" as KEY
|
|
participant "Config" as CFG
|
|
participant "Logger" as LOG
|
|
participant "GM_xmlhttpRequest" as GMX
|
|
participant "Timer" as TMR
|
|
|
|
activate CL
|
|
CL -> API : command, attempt=0
|
|
activate API
|
|
|
|
API -> CFG : get('api.maxRetries') / get('api.timeout')
|
|
CFG --> API : maxRetries / timeout
|
|
API -> KEY : _getBridgeKey()
|
|
KEY --> API : bridgeKey
|
|
|
|
API -> LOG : trace("GM_xmlhttpRequest details", {method:'POST', url, timeout, hasKey, attempt})
|
|
LOG --> API : ok
|
|
|
|
API -> GMX : POST { url:command.url, headers:{X-Bridge-Key,Content-Type}, data:JSON.stringify(command), timeout }
|
|
alt onload 2xx
|
|
GMX --> API : response {status 2xx, responseText}
|
|
API --> CL : response
|
|
else onload error status
|
|
GMX --> API : response {status !2xx, statusText}
|
|
API --> CL : throws Error(`API Error ${status}: ${statusText}`)
|
|
else onerror (network)
|
|
GMX --> API : error
|
|
alt attempt < maxRetries
|
|
API -> LOG : warn("Network error, retrying", {nextDelay})
|
|
API -> TMR : setTimeout(1000*(attempt+1))
|
|
TMR --> API : wake
|
|
API -> API : return _api(command, attempt+1)
|
|
API --> CL : bubbled result
|
|
else max retries exceeded
|
|
API -> LOG : error("Network error, max retries exceeded")
|
|
API --> CL : throws Error(`Network error after ${attempt+1} attempts`)
|
|
end
|
|
else ontimeout
|
|
GMX --> API : timeout
|
|
API -> LOG : error("API request timed out", {timeout, action})
|
|
API --> CL : throws Error(`API timeout after ${timeout}ms`)
|
|
end
|
|
|
|
deactivate API
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: _getBridgeKey() ==============================================
|
|
@startuml
|
|
title command-executor:_getBridgeKey(): \n Read bridge key from config or prompt; optionally persist
|
|
|
|
participant "Caller" as CL
|
|
participant "_getBridgeKey()" as KEY
|
|
participant "Config" as CFG
|
|
participant "Logger" as LOG
|
|
participant "Prompt" as PR
|
|
participant "Confirm" as CF
|
|
|
|
activate CL
|
|
CL -> KEY : initial request
|
|
activate KEY
|
|
|
|
KEY -> CFG : get('api.bridgeKey')
|
|
CFG --> KEY : key | undefined
|
|
alt key present
|
|
KEY -> LOG : trace("Using saved bridge key from config")
|
|
LOG --> KEY : ok
|
|
KEY --> CL : key
|
|
else key missing
|
|
KEY -> LOG : warn("Bridge key not found, prompting user")
|
|
LOG --> KEY : ok
|
|
KEY -> PR : prompt("Enter your bridge key…")
|
|
PR --> KEY : key | ""
|
|
alt empty string
|
|
KEY -> LOG : error("User did not provide bridge key")
|
|
KEY --> CL : throws Error('Bridge key required when API is enabled')
|
|
else provided
|
|
KEY -> CF : confirm("Save this bridge key?")
|
|
CF --> KEY : yes/no
|
|
alt yes
|
|
KEY -> CFG : set('api.bridgeKey', key)
|
|
CFG --> KEY : ok
|
|
KEY -> LOG : info("Bridge key saved to config")
|
|
else no
|
|
KEY -> LOG : info("Bridge key accepted for this session only")
|
|
end
|
|
KEY --> CL : key
|
|
end
|
|
end
|
|
|
|
deactivate KEY
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: _success(response, command, el, isMock, label) ================
|
|
@startuml
|
|
title command-executor:_success(response, command, el, isMock, label): \n Parse body, render status, route to output handlers
|
|
|
|
participant "Caller" as CL
|
|
participant "_success(...)" as OK
|
|
participant "JSON" as JS
|
|
participant "_status(...)" as STAT
|
|
participant "_handleGetFile(...)" as HGF
|
|
participant "_handleListFiles(...)" as HLF
|
|
|
|
activate CL
|
|
CL -> OK : response, command, el, isMock, label
|
|
activate OK
|
|
|
|
OK -> JS : JSON.parse(response.responseText || "{}")
|
|
alt parse ok
|
|
JS --> OK : data
|
|
else parse error
|
|
JS --> OK : { message: "Operation completed" }
|
|
end
|
|
|
|
OK -> STAT : _status(el, isMock?'MOCK':'SUCCESS', {action, details: data.message || 'Completed successfully', label})
|
|
STAT --> OK : rendered
|
|
|
|
alt command.action == "get_file"
|
|
OK -> HGF : _handleGetFile(data, label)
|
|
HGF --> OK : stored
|
|
else command.action == "list_files"
|
|
OK -> HLF : _handleListFiles(data, label)
|
|
HLF --> OK : stored
|
|
end
|
|
|
|
OK --> CL : { success:true, data, isMock }
|
|
deactivate OK
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: _error(error, command, el, label) =============================
|
|
@startuml
|
|
title command-executor:_error(error, command, el, label): \n Render error status and return failure object
|
|
|
|
participant "Caller" as CL
|
|
participant "_error(...)" as ERR
|
|
participant "_status(...)" as STAT
|
|
|
|
activate CL
|
|
CL -> ERR : error, command, el, label
|
|
activate ERR
|
|
|
|
ERR -> STAT : _status(el, 'ERROR', {action: command.action, details: error.message, label})
|
|
STAT --> ERR : rendered
|
|
|
|
ERR --> CL : { success:false, error: error.message }
|
|
deactivate ERR
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: _status(el, type, data) ======================================
|
|
@startuml
|
|
title command-executor:_status(el, type, data): \n Append styled status div to message element
|
|
|
|
participant "Caller" as CL
|
|
participant "_status(...)" as STAT
|
|
participant "_color(type)" as CLR
|
|
participant "DOM" as DOM
|
|
|
|
activate CL
|
|
CL -> STAT : el, type, data
|
|
activate STAT
|
|
|
|
STAT -> CLR : _color(type)
|
|
CLR --> STAT : hex color
|
|
STAT -> DOM : createElement('div'); set styles (left border color)
|
|
DOM --> STAT : div
|
|
STAT -> DOM : set textContent `${label||action} — ${type}: details`
|
|
DOM --> STAT : ready
|
|
STAT -> DOM : appendChild(el, div)
|
|
DOM --> STAT : appended
|
|
|
|
STAT --> CL : (void)
|
|
deactivate STAT
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: _handleGetFile(data, label) ===================================
|
|
@startuml
|
|
title command-executor:_handleGetFile(data, label): \n Extract content and store for paste-back
|
|
|
|
participant "Caller" as CL
|
|
participant "_handleGetFile(...)" as HGF
|
|
participant "Logger" as LOG
|
|
|
|
activate CL
|
|
CL -> HGF : data, label
|
|
activate HGF
|
|
|
|
HGF -> HGF : content = data?.content?.data || data?.content || data?.result?.content?.data || data?.result?.content
|
|
alt content missing
|
|
HGF -> LOG : warn("get_file response missing content field")
|
|
else content present
|
|
HGF -> HGF : window.AI_REPO_RESPONSES ||= []; push({label, content})
|
|
HGF -> LOG : verbose("File content stored for paste-back", {label, contentLength})
|
|
end
|
|
|
|
HGF --> CL : (void)
|
|
deactivate HGF
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: _handleListFiles(data, label) =================================
|
|
@startuml
|
|
title command-executor:_handleListFiles(data, label): \n Build fenced listing of files and store for paste-back
|
|
|
|
participant "Caller" as CL
|
|
participant "_handleListFiles(...)" as HLF
|
|
participant "Logger" as LOG
|
|
|
|
activate CL
|
|
CL -> HLF : data, label
|
|
activate HLF
|
|
|
|
HLF -> HLF : files = data?.files || data?.result?.files
|
|
alt files is Array
|
|
HLF -> HLF : listing = "```text\\n" + map(files)->path/name + "\\n```"
|
|
HLF -> HLF : window.AI_REPO_RESPONSES ||= []; push({label, content: listing})
|
|
HLF -> LOG : verbose("File listing stored", {label, fileCount: files.length})
|
|
else invalid files
|
|
HLF -> LOG : warn("list_files response missing files array")
|
|
end
|
|
|
|
HLF --> CL : (void)
|
|
deactivate HLF
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: delay(ms) =====================================================
|
|
@startuml
|
|
title command-executor:delay(ms): \n Resolve after a timeout
|
|
|
|
participant "Caller" as CL
|
|
participant "delay(ms)" as DLY
|
|
participant "Timer" as TMR
|
|
|
|
activate CL
|
|
CL -> DLY : ms
|
|
activate DLY
|
|
DLY -> TMR : setTimeout(ms)
|
|
TMR --> DLY : wake
|
|
DLY --> CL : (void)
|
|
deactivate DLY
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== LEGEND ===============================================================
|
|
@startuml
|
|
legend bottom
|
|
== command-executor 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).
|
|
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., "execute()", "_api()", "_getBridgeKey()", "_success()", "_error()", "_status()", "_handleGetFile()", "_handleListFiles()", "GM_xmlhttpRequest", "JSON", "Config", "Logger", "Timer", "DOM", "Prompt", "Confirm").
|
|
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., exact error text patterns, parameter names).
|
|
|
|
• Color palette (soft pastels)
|
|
• Use --> for returns; -> for calls.
|
|
• Participants use quoted method names for internals (e.g., "execute()"), and plain nouns for systems ("JSON", "localStorage", "GM_xmlhttpRequest", "Prompt", "Confirm").
|
|
• Keep this legend at the end of the file to standardize edits.
|
|
endlegend
|
|
@enduml
|