Skip to content

feat: dashboard - #2

Merged
aiedwardyi merged 8 commits into
mainfrom
feat/dashboard
Jul 16, 2026
Merged

feat: dashboard#2
aiedwardyi merged 8 commits into
mainfrom
feat/dashboard

Conversation

@aiedwardyi

@aiedwardyi aiedwardyi commented Jul 15, 2026

Copy link
Copy Markdown
Owner

What changed

  • Replaced the dashboard with the production single-file design and retained its mock fallback and theme persistence.
  • Added the nested state contract, per-task JSONL logs, cancellation, and disabled loop, answer, and message responses.
  • Kept the daemon local-only and dependency-free.

Verification

  • Ran node --check src\daemon.js and node --check src\store.js.
  • Verified live dispatch, completion, transcript logs, cancellation, offline state, theme persistence, and browser runtime behavior.
  • Confirmed the required tracked-file scrub has zero matches.

Review fixes applied

  • 7e5b738: cancellation state, failure reasons, disabled controls, and dashboard request guards.
  • b6d7f14: timeout reason precedence.
  • 3d675ec: events feed and timeout normalization.
  • befca60: bounded events tailing, cancellation event kind, and best-effort event writes.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Adds a complete AgentLoop browser command center with themed task views, polling, offline preview, task controls, messages, events, and transcript access. The daemon now returns structured dashboard state, tracks worker activity and completion reasons, persists logs, serves log data, and supports task cancellation.

Changes

AgentLoop command center

Layer / File(s) Summary
Dashboard shell and state rendering
public/index.html
Adds the themed multi-panel dashboard, mock preview, offline handling, polling, task views, messages, events, and status rendering.
Daemon state and completion data
src/daemon.js
Reshapes /api/state, enriches recent tasks from result files, tracks activity and tool calls, and records cancellation or failure reasons.
Worker logs and task control
src/store.js, src/daemon.js
Persists per-task logs, serves bounded transcript data, validates dispatch engines, and adds cancellation routing and worker termination.
Dashboard controls and transcript UI
public/index.html
Adds running-task cancellation, transcript rendering, theme controls, form wiring, and the window.__al development hook.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BrowserDashboard
  participant DaemonHTTPServer
  participant LogStore
  participant TaskWorker
  BrowserDashboard->>DaemonHTTPServer: GET /api/state
  DaemonHTTPServer-->>BrowserDashboard: Return structured dashboard state
  BrowserDashboard->>DaemonHTTPServer: GET /api/log/task-id
  DaemonHTTPServer->>LogStore: Read bounded task log
  LogStore-->>DaemonHTTPServer: Return transcript lines
  DaemonHTTPServer-->>BrowserDashboard: Return transcript data
  BrowserDashboard->>DaemonHTTPServer: POST /api/cancel
  DaemonHTTPServer->>TaskWorker: Terminate active worker
Loading

Possibly related PRs


Comment @coderabbitai help to get the list of available commands.

@aiedwardyi
aiedwardyi marked this pull request as ready for review July 15, 2026 13:40
@aiedwardyi

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: e6b9ebc66b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@public/index.html`:
- Around line 1262-1285: Update renderRecent and the corresponding expansion
rendering near the alternate recent-task view to include task.prompt and
result.reason when a task is expanded, while preserving the existing summary
display and collapsed behavior. Read the prompt from task.prompt and the
completion reason from result.reason, and render both with the existing escaping
and expanded-task state handling.
- Around line 1019-1043: Update poll to prevent overlapping invocations from
applying stale results: use a sequence token or equivalent serialization
covering both fetch success and fallback rendering, and discard any response
whose token is no longer current before mutating pollN, lastState, mockActive,
liveEverOk, offline status, or rendering. Apply the same protection to the
related invocation path around the additional poll logic.
- Around line 1109-1122: Remove or disable the message, loop, and answer
controls and their associated flows, including handleAnswer and the code around
the listed UI sections, unless the daemon exposes them through an explicit
capabilities contract. Ensure the frontend no longer POSTs to routes that
src/daemon.js rejects unconditionally; if retained, gate each control and
request on the corresponding capability.
- Around line 671-733: Update the drawer and newPanel dialog lifecycle,
including the corresponding code around their open/close handlers, so closed
panels are removed from the accessibility and keyboard trees using native dialog
behavior or synchronized inert/aria-hidden state. When either panel opens, move
focus into it and contain Tab/Shift+Tab navigation within that panel; restore
focus to the triggering control on close and preserve equivalent behavior for
the additional dialog section noted in the comment.
- Around line 1468-1492: Update openDrawer to track the currently opened task
and ignore fetch results or errors that no longer match that task. Before
applying either the success or catch UI updates, verify the response belongs to
the current drawer task ID, or abort the previous request when opening a new
task, while preserving the existing transcript and fallback behavior for the
active task.
- Around line 1607-1623: Update the `#taskForm` submit handler to disable its
submit button immediately before awaiting post('/api/dispatch', body), keep it
disabled through success and error handling, and re-enable it when the request
settles so repeated submissions cannot enqueue duplicate tasks.

In `@src/daemon.js`:
- Around line 236-260: Update the result construction around cancelled,
workerExitedNonzero, invalidLoopResult, details.forceFailed, and
details.timedOut so every failed completion path persists a distinct
result.reason. Preserve the existing cancellation and nonzero-exit reasons, and
add corresponding reasons for timeout, forced failure, and invalid LOOP_RESULT
outcomes so dashboardRecentTask() can expose them.
- Around line 236-243: Update the status classification and related event/count
handling around cancelled, workerExitedNonzero, invalidLoopResult, and the
corresponding logic at the alternate range so user cancellations produce the
distinct `cancelled` status. Ensure cancelled tasks do not emit `fail` or
increment the daemon’s failed count, while preserving failure handling for force
failures, timeouts, nonzero exits, and invalid loop results.

In `@src/store.js`:
- Around line 165-172: Update appendLogLine to bound per-task log growth and
remove synchronous filesystem work from the worker-output path. Add
retention/rotation or enforce a per-task byte cap, and replace
ensureDirs()/appendFileSync() with queued asynchronous writes or a write stream
while preserving task-specific log paths and line formatting.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6513b117-336e-466b-9170-a24f0e37c829

📥 Commits

Reviewing files that changed from the base of the PR and between e6b9ebc and 56aae13.

📒 Files selected for processing (3)
  • public/index.html
  • src/daemon.js
  • src/store.js
📜 Review details
🧰 Additional context used
🧬 Code graph analysis (1)
src/daemon.js (1)
src/prompts.js (1)
  • taskPrompt (13-15)
🪛 ast-grep (0.44.1)
src/store.js

[warning] 176-176: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(logPath(id), 'utf8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)

public/index.html

[error] 776-776: React's useState should not be directly called
Context: setTimeout(() => { el.textContent = ''; }, 4000)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 1030-1030: React's useState should not be directly called
Context: setOffline(!(s.daemon && s.daemon.alive))
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 1034-1034: React's useState should not be directly called
Context: setOffline(true)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 1039-1039: React's useState should not be directly called
Context: setOffline(false)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 1133-1133: React's useState should not be directly called
Context: setTimeout(() => el.classList.remove('ping'), 1600)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 1225-1233: React's useState should not be directly called
Context: setTimeout(() => {
if ((armed.get(id) || 0) <= Date.now()) {
armed.delete(id);
if (btn.isConnected && !btn.disabled) {
btn.classList.remove('armed');
btn.textContent = 'Cancel';
}
}
}, 3100)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 1529-1529: React's useState should not be directly called
Context: setTheme('tokyo-night')
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 1645-1645: React's useState should not be directly called
Context: setInterval(poll, 2000)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 1646-1646: React's useState should not be directly called
Context: setInterval(tick, 1000)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[warning] 738-740: Avoid hand-rolled HTML escaping (replacing characters with HTML entities); use a vetted encoder/sanitizer such as DOMPurify or sanitize-html.
Context: String(s ?? '').replace(/[&<>"']/g, c => (
{'&':'&','<':'<','>':'>','"':'"',"'":'&#39;'}[c]
))
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(manual-sanitization)


[warning] 812-818: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
Context: menu.innerHTML = '

Theme
' + THEMES.map(([id, name]) =>
'' +
'' +
'' + esc(name) + '' +
'&#10003;' +
''
).join('')
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(inner-outer-html)


[warning] 1076-1076: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
Context: $('#slDaemon').innerHTML = left
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(inner-outer-html)


[warning] 1078-1078: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
Context: $('#slDone').innerHTML = done === null ? '' : '' + done + ' done all-time'
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(inner-outer-html)


[warning] 1091-1106: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
Context: $('#blockedList').innerHTML = arr.map(x => {
const id = esc(x?.id);
return '

' +
'
' +
'waiting' +
'' + esc(x?.title || 'untitled') + '' +
'' + id + '' +
'
' +
'

' + esc(x?.question || '(no question text)') + '

' +
'
' +
'<textarea id="ans-' + id + '" placeholder="Type your answer" aria-label="Answer for ' + esc(x?.title || id) + '"></textarea>' +
'Send answer' +
'
' +
'
ctrl+enter to send
' +
'';
}).join('')
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(inner-outer-html)


[warning] 1165-1185: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
Context: $('#runningList').innerHTML = arr.length ? arr.map(t => {
const id = esc(t?.id);
const isLoop = t?.type === 'loop';
const engine = t?.engine || 'codex';
return '

' +
'
' +
'' + (isLoop ? 'loop' : 'task') + '' +
'' + esc(t?.title || 'untitled') + '' +
'' + id + '' +
'Cancel' +
'
' +
'
' +
'' + esc(engine) + ':' + esc(t?.model || 'default') + '' +
'-' +
'' + (num(t?.toolCalls) ?? 0) + ' tool calls' +
(isLoop ? 'cycle ' + (num(t?.cycle) ?? '?') + '/' + (num(t?.maxCycles) ?? '?') + '' : '') +
'
' +
(isLoop ? segbarHtml(t) : '') +
'
idle
' +
'';
}).join('') : hush('moon', 'All quiet - dispatch a task or start a loop.')
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(inner-outer-html)


[warning] 1242-1249: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
Context: $('#queueList').innerHTML = arr.length ? arr.map(t =>
'

' +
'' + esc(t?.title || 'untitled') + '' +
'' + esc(t?.type === 'loop' ? 'loop' : (t?.model || 'task')) + '' +
'' + esc(t?.id) + '' +
'' + fmtRel(t?.createdAt) + '' +
'
'
).join('') : hush('inbox', 'Queue is clear.')
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(inner-outer-html)


[warning] 1269-1285: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
Context: $('#recentList').innerHTML = arr.length ? arr.map(t => {
const id = esc(t?.id);
const st = t?.status ?? t?.result?.status;
const sum = t?.summary ?? t?.result?.summary ?? '';
const [pc, pt] = statusPill(st);
return '

' +
'' + esc(pt) + '' +
'
' +
'
' + esc(t?.title || 'untitled') + '' + id + '
' +
'
' + esc(sum || '(no summary)') + '
' +
'
' +
'
' +
'
' + fmtCost(t?.costUsd ?? t?.result?.costUsd) + '
' +
'
' + fmtDur(t?.durationMs ?? t?.result?.durationMs) + '
' +
'
' + fmtRel(t?.finishedAt ?? t?.result?.finishedAt) + '
' +
'
';
}).join('') : hush('check', 'Nothing finished yet.')
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(inner-outer-html)


[warning] 1320-1335: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
Context: box.innerHTML = show.length ? show.map(m => {
const id = m?.id ?? '';
const role = roleOf(m?.from);
const fresh = pollN > 1 && seenMsg.get(id) === pollN;
const linked = !!(m?.taskId && blockedIds.has(m.taskId));
const wid = role === 'worker' ? (String(m?.from || '').split(':')[1] || '') : '';
return '<article class="bubble r-' + role + (fresh ? ' fresh' : '') + (linked ? ' linked' : '') + '"' +
' data-mid="' + esc(id) + '"' + (linked ? ' data-task="' + esc(m.taskId) + '" title="Jump to the question"' : '') + '>' +
'

' +
'' + role + '' +
(wid ? '' + esc(wid) + '' : '') +
'' + fmtRel(m?.ts) + '' +
'new' +
(linked ? 'waiting for your answer' : '') +
'
' + esc(trunc(m?.text, 600)) + '
';
}).join('') : hush('chat', msgFilter === 'all' ? 'No messages yet.' : 'Nothing matches this filter.')
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(inner-outer-html)


[warning] 1351-1355: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
Context: $('#eventList').innerHTML = arr.length ? arr.map(e =>
'

  • ' + fmtRel(e?.ts, true) + '' +
    '' + esc(e?.kind || 'info') + '' +
    '' + esc(e?.text) + '
  • '
    ).join('') : '
  • ' + hush('pulse', 'No activity yet.') + '
  • '
    Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

    (inner-outer-html)


    [warning] 1480-1480: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
    Context: body.innerHTML = lines.length ? renderTranscript(lines) : '

    Transcript is empty.
    '
    Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

    (inner-outer-html)


    [warning] 1486-1486: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
    Context: body.innerHTML = renderTranscript(MOCK_LOG)
    Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

    (inner-outer-html)


    [warning] 1645-1645: Avoid using the initial state variable in setState
    Context: setInterval(poll, 2000)
    Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

    (setstate-same-var)


    [warning] 1646-1646: Avoid using the initial state variable in setState
    Context: setInterval(tick, 1000)
    Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

    (setstate-same-var)

    src/daemon.js

    [error] 371-375: React's useState should not be directly called
    Context: setTimeout(() => {
    timedOut = true;
    captureLine(Worker timed out after ${timeoutMinutes} minutes.);
    terminateWorker(child);
    }, timeoutMinutes * 60 * 1000)
    Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

    (usestate-direct-usage)


    [warning] 446-446: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
    Context: fs.readFileSync(resultPath, 'utf8')
    Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

    (detect-non-literal-fs-filename)

    🪛 HTMLHint (1.9.2)
    public/index.html

    [warning] 595-595: The type attribute must be present on elements.

    (button-type-require)


    [warning] 610-610: The type attribute must be present on

    elements.

    (button-type-require)


    [warning] 631-631: The type attribute must be present on

    elements.

    (button-type-require)


    [warning] 632-632: The type attribute must be present on

    elements.

    (button-type-require)


    [warning] 633-633: The type attribute must be present on

    elements.

    (button-type-require)


    [warning] 651-651: The type attribute must be present on

    elements.

    (button-type-require)


    [warning] 676-676: The type attribute must be present on

    elements.

    (button-type-require)


    [warning] 686-686: The type attribute must be present on

    elements.

    (button-type-require)


    [warning] 639-639: No matching [ label ] tag found.

    (input-requires-label)

    🔇 Additional comments (5)
    src/store.js (3)

    16-16: LGTM!

    Also applies to: 54-54, 319-320


    161-167: 🔒 Security & Privacy

    No path traversal issue here. /api/log/:id rejects anything outside [A-Za-z0-9_-]+, and task IDs are generated internally as t-<hex>, so logPath() never receives an untrusted value.

    			> Likely an incorrect or invalid review comment.
    

    175-183: 🚀 Performance & Scalability

    Need bounded reads at the I/O boundary

    readFileSync(...).split(...) still loads the entire log before slicing the last 200 lines. If limit can come from user input, also guard it to a finite integer; otherwise keep the comment focused on the full-file read path.

    public/index.html (1)

    2-580: LGTM!

    Also applies to: 581-616, 735-1017, 1045-1108, 1124-1261, 1289-1467, 1514-1557, 1575-1598, 1649-1654

    src/daemon.js (1)

    12-12: LGTM!

    Also applies to: 43-101, 327-382, 406-406, 444-528, 595-707

    Comment thread public/index.html
    Comment thread public/index.html
    Comment thread public/index.html
    Comment thread public/index.html
    Comment thread public/index.html
    Comment thread public/index.html
    Comment thread src/daemon.js Outdated
    Comment thread src/daemon.js Outdated
    Comment thread src/store.js

    @chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    💡 Codex Review

    Here are some automated review suggestions for this pull request.

    Reviewed commit: 56aae137b3

    ℹ️ About Codex in GitHub

    Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

    • Open a pull request for review
    • Mark a draft as ready
    • Comment "@codex review".

    If Codex has suggestions, it will comment; otherwise it will react with 👍.

    Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

    Comment thread src/daemon.js Outdated
    Comment thread src/daemon.js
    Comment thread src/store.js
    @chatgpt-codex-connector

    Copy link
    Copy Markdown

    You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

    @coderabbitai coderabbitai Bot left a comment

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 1

    🤖 Prompt for all review comments with AI agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    Inline comments:
    In `@src/daemon.js`:
    - Around line 245-248: Update the reason-selection logic in finish() so
    details.timedOut is evaluated before details.reason, ensuring a worker timeout
    produces timed_out even when an earlier stdin failure supplied
    worker_input_failed. Apply the same precedence change to the corresponding logic
    in the additionally referenced section, while preserving existing reasons for
    non-timeout outcomes.
    

    ℹ️ Review info
    ⚙️ Run configuration

    Configuration used: Organization UI

    Review profile: ASSERTIVE

    Plan: Pro

    Run ID: 4f1fde6b-631c-479e-b662-7903848504ab

    📥 Commits

    Reviewing files that changed from the base of the PR and between 56aae13 and 7e5b738.

    📒 Files selected for processing (2)
    • public/index.html
    • src/daemon.js
    📜 Review details
    🧰 Additional context used
    🪛 ast-grep (0.44.1)
    public/index.html

    [warning] 1496-1496: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
    Context: body.innerHTML = lines.length ? renderTranscript(lines) : '

    Transcript is empty.
    '
    Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

    (inner-outer-html)

    🔇 Additional comments (2)
    public/index.html (1)

    398-400: LGTM!

    Also applies to: 716-716, 740-740, 1014-1014, 1115-1115, 1273-1292, 1482-1501, 1512-1512, 1547-1549, 1622-1622, 1631-1648

    src/daemon.js (1)

    241-244: LGTM!

    Also applies to: 249-290, 327-353, 371-397, 427-427, 491-494

    Comment thread src/daemon.js Outdated

    Copilot AI left a comment

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Pull request overview

    This PR updates AgentLoop’s local daemon + dashboard to a production-ready, single-file UI and expands the daemon/state contract to support per-task transcript logs and cancellation.

    Changes:

    • Added per-task NDJSON transcript logging in the store and exposed it via a new /api/log/:id endpoint.
    • Implemented task cancellation and expanded daemon state payload to match the new dashboard’s nested contract.
    • Introduced a new single-file dashboard (public/index.html) with mock fallback + theme persistence.

    Reviewed changes

    Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

    File Description
    src/store.js Adds state/logs directory plus append/read helpers for per-task NDJSON transcripts.
    src/daemon.js Records worker output to per-task logs, adds /api/log/:id + /api/cancel, and reshapes /api/state.
    public/index.html New single-file dashboard implementing the updated state contract, transcript drawer, and theme persistence.

    💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

    Comment thread src/store.js
    Comment thread src/store.js
    Comment thread public/index.html
    Comment thread public/index.html
    Comment thread public/index.html

    @coderabbitai coderabbitai Bot left a comment

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 1

    Caution

    Some comments are outside the diff and can’t be posted inline due to platform limitations.

    ⚠️ Outside diff range comments (1)
    src/daemon.js (1)

    12-12: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

    Cap stored log files on write
    src/store.js::appendLogLine() appends every worker line to disk, and readLogLines() only limits what the API returns. Add per-task or global rotation/byte limits so a noisy worker can’t fill local disk.

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@src/daemon.js` at line 12, Update appendLogLine in src/store.js to enforce a
    per-task or global log rotation/size limit while writing worker output, rather
    than relying only on readLogLines truncation. Reuse the existing maxLogLines
    configuration where appropriate, and ensure older log content is discarded or
    rotated before continued appends so noisy workers cannot exhaust local disk.
    
    🤖 Prompt for all review comments with AI agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    Inline comments:
    In `@src/daemon.js`:
    - Around line 265-266: Update the timeout handling around spawnWorker() and
    fallbackSummary() to compute the normalized task timeout once using the same
    Math.max(1, Number(...) || 45) behavior, then reuse that value for both the
    worker timer and timeout summary. Ensure invalid, missing, zero, or negative
    configuration values produce matching durations in the dashboard and actual
    wait.
    
    ---
    
    Outside diff comments:
    In `@src/daemon.js`:
    - Line 12: Update appendLogLine in src/store.js to enforce a per-task or global
    log rotation/size limit while writing worker output, rather than relying only on
    readLogLines truncation. Reuse the existing maxLogLines configuration where
    appropriate, and ensure older log content is discarded or rotated before
    continued appends so noisy workers cannot exhaust local disk.
    

    ℹ️ Review info
    ⚙️ Run configuration

    Configuration used: Organization UI

    Review profile: ASSERTIVE

    Plan: Pro

    Run ID: 9833c4db-7d62-49dd-b8f9-4996817e6a4a

    📥 Commits

    Reviewing files that changed from the base of the PR and between 7e5b738 and b6d7f14.

    📒 Files selected for processing (1)
    • src/daemon.js
    📜 Review details
    🔇 Additional comments (1)
    src/daemon.js (1)

    43-101: LGTM!

    Also applies to: 236-264, 272-301, 329-342, 351-355, 356-372, 375-400, 422-429, 461-548, 586-597, 615-624, 635-701, 713-732

    Comment thread src/daemon.js

    @chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    💡 Codex Review

    Here are some automated review suggestions for this pull request.

    Reviewed commit: b6d7f143fb

    ℹ️ About Codex in GitHub

    Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

    • Open a pull request for review
    • Mark a draft as ready
    • Comment "@codex review".

    If Codex has suggestions, it will comment; otherwise it will react with 👍.

    Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

    Comment thread src/daemon.js Outdated

    @coderabbitai coderabbitai Bot left a comment

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 3

    🤖 Prompt for all review comments with AI agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    Inline comments:
    In `@src/daemon.js`:
    - Around line 543-545: Update the cancel branch in the event-building logic to
    explicitly set the cancellation-specific kind, matching the distinct event kind
    used by the dashboard for cancelled tasks, while preserving the existing text
    and value fields.
    - Line 697: Guard the store.appendEvent call in the async dispatch handler so
    synchronous write failures are caught and handled without leaving the request
    unresolved or crashing the daemon. Preserve the already-successful task enqueue
    behavior, and ensure the handler sends an appropriate client response when
    appendEvent fails.
    - Around line 550-575: Update recentEvents() to avoid readFileSync loading the
    entire unbounded events log on each poll. Implement a bounded tail read that
    retrieves only enough recent lines or bytes to identify the last 30 events,
    while preserving reverse chronological order, malformed-line skipping,
    dashboardEvent transformation, and [] on read failure.
    

    ℹ️ Review info
    ⚙️ Run configuration

    Configuration used: Organization UI

    Review profile: ASSERTIVE

    Plan: Pro

    Run ID: 28c09e97-a780-4c5d-b120-60818076a139

    📥 Commits

    Reviewing files that changed from the base of the PR and between b6d7f14 and 3d675ec.

    📒 Files selected for processing (1)
    • src/daemon.js
    📜 Review details
    🧰 Additional context used
    🪛 ast-grep (0.44.1)
    src/daemon.js

    [error] 392-396: React's useState should not be directly called
    Context: setTimeout(() => {
    timedOut = true;
    captureLine(Worker timed out after ${timeoutMin} minutes.);
    terminateWorker(child);
    }, timeoutMin * 60 * 1000)
    Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

    (usestate-direct-usage)


    [warning] 551-551: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
    Context: fs.readFileSync(store.paths.events, 'utf8')
    Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

    (detect-non-literal-fs-filename)

    🔇 Additional comments (1)
    src/daemon.js (1)

    217-224: LGTM!

    Also applies to: 392-397

    Comment thread src/daemon.js
    Comment thread src/daemon.js
    Comment thread src/daemon.js

    @chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    💡 Codex Review

    Here are some automated review suggestions for this pull request.

    Reviewed commit: 3d675ecd4f

    ℹ️ About Codex in GitHub

    Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

    • Open a pull request for review
    • Mark a draft as ready
    • Comment "@codex review".

    If Codex has suggestions, it will comment; otherwise it will react with 👍.

    Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

    Comment thread src/daemon.js
    Comment thread src/daemon.js Outdated
    @aiedwardyi
    aiedwardyi requested a review from Copilot July 16, 2026 06:51
    @aiedwardyi

    Copy link
    Copy Markdown
    Owner Author

    @codex review

    @coderabbitai coderabbitai Bot left a comment

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    ♻️ Duplicate comments (1)
    src/daemon.js (1)

    517-549: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

    Cancel events still map to generic info kind — regression of previously fixed issue.

    Line 545 explicitly sets kind: 'info' for cancel events, matching the exact bug flagged in an earlier review round (cancelled events losing their distinct styling because they fall back to info), which was reportedly fixed in commit befca60 by using kind: 'cancel'. In this rewritten dashboardEvent(), that fix appears to have been lost — every other terminal branch (queue, start, done, fail) gets a dedicated kind, but cancel is back to the indistinguishable default.

    🐛 Proposed fix
       if (event.type === 'cancel') {
    -    return { ...value, kind: 'info', text: `${task} cancelled${reason}` };
    +    return { ...value, kind: 'cancel', text: `${task} cancelled${reason}` };
       }
    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@src/daemon.js` around lines 517 - 549, Update the cancel branch in
    dashboardEvent so cancelled events use the dedicated “cancel” kind instead of
    “info”; preserve the existing cancellation text and reason handling.
    
    🤖 Prompt for all review comments with AI agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    Duplicate comments:
    In `@src/daemon.js`:
    - Around line 517-549: Update the cancel branch in dashboardEvent so cancelled
    events use the dedicated “cancel” kind instead of “info”; preserve the existing
    cancellation text and reason handling.
    

    ℹ️ Review info
    ⚙️ Run configuration

    Configuration used: Organization UI

    Review profile: ASSERTIVE

    Plan: Pro

    Run ID: 2a5634fc-dd98-488f-9098-ed9da8397a2d

    📥 Commits

    Reviewing files that changed from the base of the PR and between b6d7f14 and befca60.

    📒 Files selected for processing (2)
    • src/daemon.js
    • src/store.js
    📜 Review details
    🧰 Additional context used
    🪛 ast-grep (0.44.1)
    src/daemon.js

    [error] 393-397: React's useState should not be directly called
    Context: setTimeout(() => {
    timedOut = true;
    captureLine(Worker timed out after ${timeoutMin} minutes.);
    terminateWorker(child);
    }, timeoutMin * 60 * 1000)
    Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

    (usestate-direct-usage)

    🔇 Additional comments (4)
    src/daemon.js (3)

    218-226: LGTM!

    Also applies to: 393-398


    551-600: LGTM!


    602-641: LGTM!

    Also applies to: 691-725

    src/store.js (1)

    189-203: LGTM!

    Copilot AI left a comment

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Pull request overview

    Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

    Comment thread src/daemon.js
    Comment thread src/store.js
    Comment thread src/daemon.js

    @chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    💡 Codex Review

    Here are some automated review suggestions for this pull request.

    Reviewed commit: befca60373

    ℹ️ About Codex in GitHub

    Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

    • Open a pull request for review
    • Mark a draft as ready
    • Comment "@codex review".

    If Codex has suggestions, it will comment; otherwise it will react with 👍.

    Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

    Comment thread src/store.js
    Comment thread public/index.html
    Comment thread src/daemon.js
    @aiedwardyi
    aiedwardyi merged commit dbe7753 into main Jul 16, 2026
    2 checks passed
    @aiedwardyi
    aiedwardyi deleted the feat/dashboard branch July 16, 2026 12:49
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Labels

    None yet

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    2 participants