collet 0.1.1

Relentless agentic coding orchestrator with zero-drop agent loops
Documentation
<script lang="ts">
	import { appStore } from '$lib/store.svelte.js';
	import { cancelSwarmAgent, extendSwarmAgent } from '$lib/events.js';

	let agent = $derived(
		appStore.selectedAgentId ? appStore.hiveAgents.get(appStore.selectedAgentId) ?? null : null
	);

	let controlBusy = $state(false);
	let controlMessage = $state<string | null>(null);

	function formatTokens(n: number): string {
		if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
		if (n >= 1_000) return (n / 1_000).toFixed(1) + 'K';
		return String(n);
	}

	function close() {
		appStore.selectedAgentId = null;
		controlMessage = null;
	}

	async function handleCancel() {
		if (!agent || controlBusy) return;
		controlBusy = true;
		controlMessage = null;
		try {
			const res = await cancelSwarmAgent('', agent.id, appStore.authToken ?? undefined);
			controlMessage = res.message;
		} catch (e) {
			controlMessage = 'Failed to cancel agent';
		}
		controlBusy = false;
	}

	async function handleExtend(extra: number) {
		if (!agent || controlBusy) return;
		controlBusy = true;
		controlMessage = null;
		try {
			const res = await extendSwarmAgent('', agent.id, extra, appStore.authToken ?? undefined);
			controlMessage = res.message;
		} catch (e) {
			controlMessage = 'Failed to extend agent';
		}
		controlBusy = false;
	}
</script>

{#if agent}
	<div class="detail-panel">
		<div class="panel-header">
			<div class="header-left">
				<span class="agent-name">{agent.name || agent.id}</span>
				{#if agent.done}
					<span class="badge" class:success={agent.success} class:fail={!agent.success}>
						{agent.success ? 'OK' : 'FAIL'}
					</span>
				{:else if agent.approaching}
					<span class="badge warn">LIMIT</span>
				{:else if agent.iteration > 0}
					<span class="badge running">RUN</span>
				{:else}
					<span class="badge pending">WAIT</span>
				{/if}
			</div>
			<button class="close-btn" onclick={close}>&times;</button>
		</div>

		<!-- Task description -->
		<div class="section">
			<div class="section-title">Task</div>
			<div class="task-text">{agent.task}</div>
		</div>

		<!-- Metrics grid -->
		<div class="metrics">
			<div class="metric">
				<span class="metric-label">Iteration</span>
				<span class="metric-value">{agent.iteration}</span>
			</div>
			<div class="metric">
				<span class="metric-label">Tool Calls</span>
				<span class="metric-value">{agent.toolCalls}</span>
			</div>
			<div class="metric">
				<span class="metric-label">In Tokens</span>
				<span class="metric-value">{formatTokens(agent.inputTokens)}</span>
			</div>
			<div class="metric">
				<span class="metric-label">Out Tokens</span>
				<span class="metric-value">{formatTokens(agent.outputTokens)}</span>
			</div>
		</div>

		<!-- Approaching limit warning -->
		{#if agent.approaching && !agent.done}
			<div class="warning-bar">
				Approaching iteration limit — {agent.approachingRemaining} remaining
			</div>
		{/if}

		<!-- Agent control actions -->
		{#if !agent.done}
			<div class="control-bar">
				<button class="control-btn cancel" onclick={handleCancel} disabled={controlBusy}>
					Cancel
				</button>
				<button class="control-btn extend" onclick={() => handleExtend(10)} disabled={controlBusy}>
					+10 iter
				</button>
				<button class="control-btn extend" onclick={() => handleExtend(25)} disabled={controlBusy}>
					+25 iter
				</button>
			</div>
		{/if}

		{#if controlMessage}
			<div class="control-message">{controlMessage}</div>
		{/if}

		<!-- Modified files -->
		{#if agent.modifiedFiles.length > 0}
			<div class="section">
				<div class="section-title">Modified Files ({agent.modifiedFiles.length})</div>
				<div class="file-list">
					{#each agent.modifiedFiles as file}
						<div class="file-entry">{file}</div>
					{/each}
				</div>
			</div>
		{/if}

		<!-- Dependencies -->
		{#if agent.dependencies.length > 0}
			<div class="section">
				<div class="section-title">Dependencies</div>
				<div class="dep-list">
					{#each agent.dependencies as dep}
						<!-- svelte-ignore a11y_click_events_have_key_events -->
						<!-- svelte-ignore a11y_no_static_element_interactions -->
						<span class="dep-tag" onclick={() => (appStore.selectedAgentId = dep)}>{dep}</span>
					{/each}
				</div>
			</div>
		{/if}

		<!-- Agent output (scrollable chat-like area) -->
		{#if agent.response}
			<div class="section output-section">
				<div class="section-title">Output</div>
				<div class="output-text">{agent.response}</div>
			</div>
		{/if}
	</div>
{/if}

<style>
	.detail-panel {
		background: var(--bg-surface);
		border: 1px solid var(--fg-dim, #333);
		border-radius: 8px;
		padding: 12px;
		display: flex;
		flex-direction: column;
		gap: 10px;
		max-height: 50vh;
		overflow-y: auto;
	}
	.panel-header {
		display: flex;
		justify-content: space-between;
		align-items: center;
	}
	.header-left {
		display: flex;
		align-items: center;
		gap: 8px;
	}
	.agent-name {
		font-weight: 700;
		font-size: 14px;
		color: var(--cyan);
	}
	.badge {
		font-size: 9px;
		font-weight: 700;
		padding: 2px 6px;
		border-radius: 3px;
		text-transform: uppercase;
	}
	.badge.success { background: var(--green); color: var(--bg); }
	.badge.fail { background: var(--red); color: var(--bg); }
	.badge.warn { background: var(--yellow); color: var(--bg); }
	.badge.running { background: var(--accent); color: var(--bg); }
	.badge.pending { background: var(--fg-dim); color: var(--bg); }

	.close-btn {
		background: none;
		border: none;
		color: var(--fg-dim);
		font-size: 18px;
		cursor: pointer;
		padding: 0 4px;
	}
	.close-btn:hover { color: var(--fg); }

	.section { display: flex; flex-direction: column; gap: 4px; }
	.section-title {
		font-size: 10px;
		font-weight: 600;
		text-transform: uppercase;
		color: var(--fg-dim);
		letter-spacing: 0.5px;
	}
	.task-text {
		font-size: 12px;
		color: var(--fg);
		line-height: 1.5;
		word-break: break-word;
	}

	.metrics {
		display: grid;
		grid-template-columns: 1fr 1fr;
		gap: 6px;
	}
	.metric {
		background: var(--bg-elevated);
		border-radius: 4px;
		padding: 6px 8px;
		display: flex;
		flex-direction: column;
	}
	.metric-label {
		font-size: 9px;
		color: var(--fg-dim);
		text-transform: uppercase;
	}
	.metric-value {
		font-size: 16px;
		font-weight: 700;
		color: var(--fg);
		font-family: var(--font-mono);
	}

	.warning-bar {
		background: color-mix(in srgb, var(--yellow) 15%, transparent);
		border: 1px solid var(--yellow);
		border-radius: 4px;
		padding: 6px 10px;
		font-size: 11px;
		color: var(--yellow);
		font-weight: 600;
	}

	.file-list {
		display: flex;
		flex-direction: column;
		gap: 2px;
	}
	.file-entry {
		font-family: var(--font-mono);
		font-size: 11px;
		color: var(--fg);
		padding: 2px 6px;
		background: var(--bg-elevated);
		border-radius: 3px;
		word-break: break-all;
	}

	.dep-list {
		display: flex;
		gap: 4px;
		flex-wrap: wrap;
	}
	.dep-tag {
		font-size: 10px;
		font-family: var(--font-mono);
		padding: 2px 8px;
		background: var(--bg-elevated);
		border-radius: 3px;
		color: var(--accent);
		cursor: pointer;
	}
	.dep-tag:hover {
		background: var(--accent);
		color: var(--bg);
	}

	.output-section {
		flex: 1;
		min-height: 0;
	}
	.output-text {
		font-family: var(--font-mono);
		font-size: 11px;
		color: var(--fg);
		line-height: 1.5;
		white-space: pre-wrap;
		word-break: break-word;
		max-height: 200px;
		overflow-y: auto;
		padding: 8px;
		background: var(--bg);
		border-radius: 4px;
	}

	.control-bar {
		display: flex;
		gap: 6px;
	}
	.control-btn {
		flex: 1;
		padding: 5px 8px;
		border: 1px solid var(--fg-dim);
		border-radius: 4px;
		font-size: 11px;
		font-weight: 600;
		cursor: pointer;
		background: var(--bg-elevated);
		color: var(--fg);
		transition: background 0.15s, border-color 0.15s;
	}
	.control-btn:hover:not(:disabled) {
		background: var(--bg-surface);
	}
	.control-btn:disabled {
		opacity: 0.4;
		cursor: not-allowed;
	}
	.control-btn.cancel {
		border-color: var(--red);
		color: var(--red);
	}
	.control-btn.cancel:hover:not(:disabled) {
		background: color-mix(in srgb, var(--red) 15%, transparent);
	}
	.control-btn.extend {
		border-color: var(--green);
		color: var(--green);
	}
	.control-btn.extend:hover:not(:disabled) {
		background: color-mix(in srgb, var(--green) 15%, transparent);
	}
	.control-message {
		font-size: 10px;
		color: var(--fg-dim);
		padding: 4px 6px;
		background: var(--bg-elevated);
		border-radius: 3px;
		text-align: center;
	}
</style>