Skip to content

Commit 2d2baac

Browse files
committed
Fix conductor execution and movement observability
1 parent 15ca278 commit 2d2baac

16 files changed

Lines changed: 390 additions & 38 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ Download packaged builds from GitHub Releases:
88

99
https://github.com/TerminallyLazy/MISCONDUCT/releases/latest
1010

11-
Current app metadata in this checkout is `0.1.17`. The public latest release page may
12-
still show older assets until tag `v0.1.17` is pushed and the desktop release workflow
11+
Current app metadata in this checkout is `0.1.18`. The public latest release page may
12+
still show older assets until tag `v0.1.18` is pushed and the desktop release workflow
1313
publishes new artifacts.
1414

1515
The GitHub Actions workflow `.github/workflows/release-desktop.yml` is configured to build macOS arm64, Windows x86_64, and Linux x86_64 packages from release tags.

RELEASE_WORKFLOW_NOTE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ It builds:
88
- Windows x86_64 NSIS installer
99
- Linux x86_64 AppImage/DEB
1010

11-
Push a release tag such as `v0.1.17` or run the workflow manually from GitHub Actions to produce MISCONDUCT release artifacts.
11+
Push a release tag such as `v0.1.18` or run the workflow manually from GitHub Actions to produce MISCONDUCT release artifacts.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "misconduct-desktop",
3-
"version": "0.1.17",
3+
"version": "0.1.18",
44
"private": true,
55
"type": "module",
66
"scripts": {

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "misconduct_desktop"
3-
version = "0.1.17"
3+
version = "0.1.18"
44
edition = "2021"
55

66
[build-dependencies]

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"productName": "MISCONDUCT",
3-
"version": "0.1.17",
3+
"version": "0.1.18",
44
"identifier": "dev.misconduct.desktop",
55
"build": {
66
"beforeDevCommand": "npm run dev",

src/main.tsx

Lines changed: 108 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ const KanbanCardSchema = z.object({
7272
phase: z.string().optional(),
7373
operator_status: z.string().optional(),
7474
operator_summary: z.string().nullable().optional(),
75+
workspace_target: z.string().nullable().optional(),
7576
workspace_path: z.string().nullable().optional(),
77+
repository_path: z.string().nullable().optional(),
7678
session_id: z.string().nullable().optional(),
7779
last_event: z.string().nullable().optional(),
7880
last_message: z.string().nullable().optional(),
@@ -259,6 +261,13 @@ type Card = {
259261
stagePosition?: StagePosition;
260262
scorePath?: string;
261263
scoreSummary?: string;
264+
workspacePath?: string;
265+
workspaceTarget?: string;
266+
repositoryPath?: string;
267+
verdictPath?: string;
268+
judgeVerdict?: unknown;
269+
refinerAttempt?: number | null;
270+
refinerMaxAttempts?: number | null;
262271
phaseHistory: PhaseHistoryEntry[];
263272
conversation: AgentConversationMessage[];
264273
intensity: number;
@@ -269,6 +278,7 @@ type ManualMovementPayload = {
269278
title: string;
270279
description?: string;
271280
identifier?: string;
281+
workspace_path?: string;
272282
};
273283

274284
const columns = ['Ready', 'In Progress', 'Human Review', 'Retry', 'Blocked', 'Done'];
@@ -846,6 +856,13 @@ function makeCardsFromKanban(kanban?: KanbanState): Card[] {
846856
stagePosition: profileStagePosition,
847857
scorePath: stringField(rawRecord, 'score_path'),
848858
scoreSummary: stringField(rawRecord, 'score_summary'),
859+
workspacePath: stringField(rawRecord, 'workspace_path'),
860+
workspaceTarget: stringField(rawRecord, 'workspace_target'),
861+
repositoryPath: stringField(rawRecord, 'repository_path'),
862+
verdictPath: stringField(rawRecord, 'verdict_path'),
863+
judgeVerdict: rawRecord.judge_verdict,
864+
refinerAttempt: typeof raw.refiner_attempt === 'number' ? raw.refiner_attempt : undefined,
865+
refinerMaxAttempts: typeof raw.refiner_max_attempts === 'number' ? raw.refiner_max_attempts : undefined,
849866
phaseHistory,
850867
conversation,
851868
intensity: intensityFor(tokens, turns, priority),
@@ -1585,6 +1602,7 @@ function App() {
15851602
onCreateMovement={payload => createMovement.mutateAsync(payload)}
15861603
movementBusy={createMovement.isPending}
15871604
movementError={createMovement.error instanceof Error ? createMovement.error.message : undefined}
1605+
liveEvents={orchestrationEvents.events}
15881606
/>
15891607
)}
15901608
{tab === 'agents' && (
@@ -1912,7 +1930,8 @@ function OrchestraFloor({
19121930
onAction,
19131931
onCreateMovement,
19141932
movementBusy,
1915-
movementError
1933+
movementError,
1934+
liveEvents
19161935
}: {
19171936
cards: Card[];
19181937
selectedCard?: Card;
@@ -1928,6 +1947,7 @@ function OrchestraFloor({
19281947
onCreateMovement: (payload: ManualMovementPayload) => Promise<unknown>;
19291948
movementBusy: boolean;
19301949
movementError?: string;
1950+
liveEvents: OrchestrationEvent[];
19311951
}) {
19321952
const counts = Object.fromEntries(
19331953
renderedStations.map(station => [
@@ -1999,6 +2019,7 @@ function OrchestraFloor({
19992019
onCreateMovement={onCreateMovement}
20002020
movementBusy={movementBusy}
20012021
movementError={movementError}
2022+
liveEvents={liveEvents}
20022023
/>
20032024
</div>
20042025
);
@@ -2159,7 +2180,8 @@ function ScoreConsole({
21592180
onAction,
21602181
onCreateMovement,
21612182
movementBusy,
2162-
movementError
2183+
movementError,
2184+
liveEvents
21632185
}: {
21642186
selectedCard?: Card;
21652187
debugPayload: string | null;
@@ -2170,12 +2192,13 @@ function ScoreConsole({
21702192
onCreateMovement: (payload: ManualMovementPayload) => Promise<unknown>;
21712193
movementBusy: boolean;
21722194
movementError?: string;
2195+
liveEvents: OrchestrationEvent[];
21732196
}) {
2174-
const [scoreTab, setScoreTab] = useState<'score' | 'events' | 'tools'>('score');
2197+
const [scoreTab, setScoreTab] = useState<'plan' | 'ledger' | 'controls'>('plan');
21752198
return (
21762199
<aside className="scoreConsole pixelPanel">
21772200
<div className="consoleTabs">
2178-
{(['score', 'events', 'tools'] as const).map(tab => (
2201+
{(['plan', 'ledger', 'controls'] as const).map(tab => (
21792202
<button key={tab} className={scoreTab === tab ? 'scoreTabButton active' : 'scoreTabButton'} onClick={() => setScoreTab(tab)}>{tab.toUpperCase()}</button>
21802203
))}
21812204
</div>
@@ -2186,20 +2209,21 @@ function ScoreConsole({
21862209
<h2>{selectedCard.identifier}</h2>
21872210
<h3>{selectedCard.title}</h3>
21882211
<StatusPill status={selectedCard.status} />
2189-
{scoreTab === 'score' && <MovementPanel card={selectedCard} />}
2190-
{scoreTab === 'events' && <MovementEvents card={selectedCard} debugPayload={debugPayload} />}
2191-
{scoreTab === 'tools' && <MovementTools card={selectedCard} busy={busy} onDebug={onDebug} onMove={onMove} onAction={onAction} />}
2212+
{scoreTab === 'plan' && <MovementPanel card={selectedCard} />}
2213+
{scoreTab === 'ledger' && <MovementEvents card={selectedCard} debugPayload={debugPayload} liveEvents={liveEvents} />}
2214+
{scoreTab === 'controls' && <MovementTools card={selectedCard} busy={busy} onDebug={onDebug} onMove={onMove} onAction={onAction} />}
21922215
</div>
21932216
) : (
21942217
<div className="selectedScore empty"><h2>No movement selected</h2><p>The orchestra is waiting for a movement.</p></div>
21952218
)}
2196-
{debugPayload && scoreTab !== 'events' && <pre className="debugPanel">{debugPayload}</pre>}
2219+
{debugPayload && scoreTab !== 'ledger' && <pre className="debugPanel">{debugPayload}</pre>}
21972220
</aside>
21982221
);
21992222
}
22002223

22012224
function ManualMovementComposer({ busy, error, onCreateMovement }: { busy: boolean; error?: string; onCreateMovement: (payload: ManualMovementPayload) => Promise<unknown> }) {
22022225
const [title, setTitle] = useState('');
2226+
const [workspacePath, setWorkspacePath] = useState(() => localStorage.getItem('misconduct.lastMovementWorkspace') || '');
22032227
const [description, setDescription] = useState('');
22042228
const [localError, setLocalError] = useState<string | null>(null);
22052229
const ready = title.trim().length > 0;
@@ -2213,9 +2237,12 @@ function ManualMovementComposer({ busy, error, onCreateMovement }: { busy: boole
22132237

22142238
try {
22152239
setLocalError(null);
2240+
const target = workspacePath.trim();
2241+
if (target) localStorage.setItem('misconduct.lastMovementWorkspace', target);
22162242
await onCreateMovement({
22172243
title: title.trim(),
2218-
description: description.trim()
2244+
description: description.trim(),
2245+
workspace_path: target || undefined
22192246
});
22202247
setTitle('');
22212248
setDescription('');
@@ -2227,49 +2254,91 @@ function ManualMovementComposer({ busy, error, onCreateMovement }: { busy: boole
22272254
return (
22282255
<form className="movementComposer" onSubmit={submit}>
22292256
<div className="movementComposerHeader">
2230-
<p className="eyebrow">Conductor intake</p>
2231-
<button className="button primary compact" disabled={busy || !ready} type="submit"><PlayCircle size={14} />Conduct</button>
2257+
<p className="eyebrow">Movement intake</p>
2258+
<button className="button primary compact" disabled={busy || !ready} type="submit"><PlayCircle size={14} />Conduct in repo</button>
22322259
</div>
22332260
<label>
22342261
Movement title
2235-
<input value={title} onChange={event => setTitle(event.target.value)} placeholder="Add a ready-to-conduct score" />
2262+
<input value={title} onChange={event => setTitle(event.target.value)} placeholder="Implement a focused change" />
22362263
</label>
22372264
<label>
2238-
Score brief
2239-
<textarea value={description} onChange={event => setDescription(event.target.value)} placeholder="Objective, constraints, repository context" />
2265+
Target directory / repo
2266+
<input value={workspacePath} onChange={event => setWorkspacePath(event.target.value)} placeholder="/Users/you/project or blank for managed workspace" />
2267+
</label>
2268+
<label>
2269+
Baton notes
2270+
<textarea value={description} onChange={event => setDescription(event.target.value)} placeholder="Objective, constraints, files, commands, and evidence expected" />
22402271
</label>
22412272
{(localError || error) && <p className="formError">{localError || error}</p>}
22422273
</form>
22432274
);
22442275
}
22452276

22462277
function MovementPanel({ card }: { card: Card }) {
2278+
const workspace = card.workspacePath || card.workspaceTarget || card.repositoryPath;
22472279
return (
22482280
<>
22492281
<div className="scoreSheet">
22502282
<div className="staffLines"><i /><i /><i /><i /><i /></div>
22512283
<div className="scoreNotes" style={{ '--movement-intensity': `${card.intensity}%` } as React.CSSProperties}>♪ ♫ ♩ ♬</div>
22522284
</div>
2285+
<CueChain card={card} />
22532286
<dl className="definitionList compact">
2287+
<div><dt>Workspace</dt><dd>{workspace ? <code>{workspace}</code> : 'managed movement workspace'}</dd></div>
22542288
<div><dt>Section</dt><dd>{card.section}</dd></div>
22552289
<div><dt>Instrument</dt><dd>{card.instrumentName}</dd></div>
22562290
<div><dt>Agent</dt><dd>{card.agent}</dd></div>
22572291
<div><dt>Backend ID</dt><dd><code>{card.backendId}</code></dd></div>
22582292
</dl>
22592293
{(card.scoreSummary || card.scorePath) && (
22602294
<div className="scoreArtifact">
2261-
<b>Conductor score</b>
2295+
<b>Conductor plan artifact</b>
22622296
{card.scoreSummary && <p>{card.scoreSummary}</p>}
22632297
{card.scorePath && <code>{card.scorePath}</code>}
22642298
</div>
22652299
)}
2300+
{card.verdictPath && (
2301+
<div className="scoreArtifact judgeArtifact">
2302+
<b>Judge verdict artifact</b>
2303+
<code>{card.verdictPath}</code>
2304+
</div>
2305+
)}
22662306
<PhaseTrace entries={card.phaseHistory} />
22672307
<AgentConversation messages={card.conversation} activeAgent={card.agent} />
22682308
<p>{card.message || card.retry || 'Waiting for the next orchestration cue.'}</p>
22692309
</>
22702310
);
22712311
}
22722312

2313+
function CueChain({ card }: { card: Card }) {
2314+
const phase = (card.phase || card.stage || '').toLowerCase();
2315+
const completed = new Set(card.phaseHistory.filter(entry => entry.status === 'completed').map(entry => String(entry.phase || '').toLowerCase()));
2316+
const cueState = (name: string) => {
2317+
if (completed.has(name)) return 'complete';
2318+
if (phase === name) return 'active';
2319+
return 'waiting';
2320+
};
2321+
const refinerLabel = card.refinerMaxAttempts ? `${card.refinerAttempt || 0}/${card.refinerMaxAttempts}` : undefined;
2322+
const cues = [
2323+
{ id: 'conductor', label: 'Conductor', detail: 'plans movement', artifact: card.scorePath, state: cueState('conductor') },
2324+
{ id: 'build', label: 'Builder', detail: 'edits workspace', artifact: card.workspacePath || card.workspaceTarget || card.repositoryPath, state: cueState('build') },
2325+
{ id: 'judge', label: 'Judge', detail: 'checks evidence', artifact: card.verdictPath, state: cueState('judge') },
2326+
{ id: 'refiner', label: 'Refiner', detail: refinerLabel ? `fixes findings ${refinerLabel}` : 'fixes findings', artifact: card.judgeVerdict ? 'judge verdict loaded' : undefined, state: cueState('refiner') }
2327+
];
2328+
2329+
return (
2330+
<div className="cueChain" aria-label="Movement cue chain">
2331+
{cues.map(cue => (
2332+
<div key={cue.id} className={`cueStep ${cue.state}`}>
2333+
<b>{cue.label}</b>
2334+
<span>{cue.detail}</span>
2335+
<small>{cue.artifact || 'awaiting artifact'}</small>
2336+
</div>
2337+
))}
2338+
</div>
2339+
);
2340+
}
2341+
22732342
function PhaseTrace({ entries }: { entries: PhaseHistoryEntry[] }) {
22742343
if (!entries.length) return null;
22752344

@@ -2308,19 +2377,32 @@ function AgentConversation({ messages, activeAgent }: { messages: AgentConversat
23082377
);
23092378
}
23102379

2311-
function MovementEvents({ card, debugPayload }: { card: Card; debugPayload: string | null }) {
2380+
function MovementEvents({ card, debugPayload, liveEvents }: { card: Card; debugPayload: string | null; liveEvents: OrchestrationEvent[] }) {
2381+
const movementEvents = liveEvents
2382+
.filter(event => {
2383+
const ids = [event.issue_id, event.issue_identifier].filter(Boolean);
2384+
return ids.includes(card.backendId) || ids.includes(card.identifier);
2385+
})
2386+
.slice(-18)
2387+
.reverse();
23122388
const events = [
23132389
['Movement', card.movement],
23142390
['Status', card.status],
23152391
['Turns', String(card.turns)],
23162392
['Notes', card.tokens.toLocaleString()],
2393+
['Workspace', card.workspacePath || card.workspaceTarget || card.repositoryPath || 'managed movement workspace'],
23172394
['Score path', card.scorePath || 'none'],
2395+
['Verdict path', card.verdictPath || 'none'],
23182396
['Retry due', card.retry || 'none'],
23192397
['Last message', card.message || 'none reported']
23202398
];
23212399
return (
23222400
<div className="movementTimeline">
23232401
{events.map(([label, value]) => <div className="timelineRow" key={label}><b>{label}</b><span>{value}</span></div>)}
2402+
<div className="liveMovementLedger">
2403+
<h3>Live pit ledger</h3>
2404+
{movementEvents.length ? movementEvents.map(event => <EventRow event={event} key={event.id} />) : <p className="subtle">No live events for this movement yet.</p>}
2405+
</div>
23242406
{debugPayload && <pre className="debugPanel inline">{debugPayload}</pre>}
23252407
</div>
23262408
);
@@ -2774,7 +2856,13 @@ function WorkflowPanel({
27742856
</div>
27752857

27762858
<aside className="workflowInspector pixelPanel">
2777-
<div className="panelHeader"><div><p className="eyebrow">Rehearsal Desk</p><h2>Judge / Refiner / Location</h2></div></div>
2859+
<div className="panelHeader"><div><p className="eyebrow">Rehearsal Desk</p><h2>Activation / Location</h2></div></div>
2860+
<div className="workflowMap">
2861+
<div><b>1. Compose</b><span>WORKFLOW.md defines the orchestra, roles, and defaults.</span></div>
2862+
<div><b>2. Rehearse</b><span>Readiness checks verify provider, auth, roster, and workspace.</span></div>
2863+
<div><b>3. Conduct</b><span>A movement targets a repo or managed workspace and starts the phase chain.</span></div>
2864+
<div><b>4. Observe</b><span>Conductor, Builder, Judge, and Refiner publish artifacts and ledger events.</span></div>
2865+
</div>
27782866
<div className="targetPreview">
27792867
<b>Backend-managed root</b>
27802868
<code>{filesQuery.data?.root || (enabled ? 'loading…' : 'waiting for desktop backend')}</code>
@@ -2807,8 +2895,8 @@ function WorkflowPanel({
28072895
</div>
28082896

28092897
<div className="resultPanel">
2810-
<h3>Judge / Refiner</h3>
2811-
{review ? <pre>{JSON.stringify(review, null, 2)}</pre> : <p className="subtle">No review yet.</p>}
2898+
<h3>Workflow validation judge</h3>
2899+
{review ? <pre>{JSON.stringify(review, null, 2)}</pre> : <p className="subtle">No validation review yet.</p>}
28122900
</div>
28132901

28142902
<div className="resultPanel">
@@ -3016,7 +3104,7 @@ function SafetyPanel() {
30163104
<section className="doc pixelPanel">
30173105
<h2><ShieldAlert size={20} /> Safety Posture</h2>
30183106
<ul className="checkList">
3019-
<li>Agent working directories are constrained to per-issue workspaces.</li>
3107+
<li>Agent working directories are explicit: a movement target repo or a managed movement workspace.</li>
30203108
<li>Workspace keys allow only alphanumeric, dot, underscore, and dash characters.</li>
30213109
<li>Secrets use environment indirection and are not emitted in public config JSON.</li>
30223110
<li>Hooks time out to prevent orchestration stalls.</li>

0 commit comments

Comments
 (0)