mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
fix(web): remove Ingest source panel from Automations tab (#2711)
* fix(web): remove Ingest source panel from Automations tab The Automations tab carried a free-form "Ingest source" composer that let users paste arbitrary content (URL, repo path, connector event, chat snippet) and turn it into a source packet plus evolution proposals. The form was confusing next to the routine/template flow on the same page, exposed an internal canonicalization concept users don't need to think about, and shipped before the surrounding evolution-proposal flow was wired into a coherent end-to-end story. Drop the UI surface only: - Remove the <section className="automations-ingest"> block, the Template / Source / Compression / Connector selects, the title/source ref/content fields, the recent-packets list, and the Ingest button. - Drop the now-dead local state (sourcePackets / sourceForm / ingestingSource), the patchSourceForm and submitSourceIngestion helpers, the SOURCE_KIND_OPTIONS / COMPRESSION_OPTIONS constants, the SourceIngestionForm type and DEFAULT_SOURCE_FORM, the /api/automation-source-packets refresh leg, and the sourcePackets side-write inside crystallizeRun. - Remove the matching .automations-ingest / .automation-ingest-* CSS block (plus the two responsive overrides) from tasks.css. - Delete the test case that drove the form in TasksView.templates.test. Backend stays intact: apps/daemon/src/automation-ingestions.ts, the POST /api/automation-ingestions route, `od automation ingest` CLI, the routine-evolution call site, and the AutomationContentPacket / AutomationSourceKind / AutomationTokenCompressionMode contracts all remain, since routine scheduling still depends on them. * fix(web): drop crystallize test assertion on removed packet list The crystallize test was asserting that the new content packet's title shows up on the page. That assertion only passed because the daemon response was being side-written into the deleted sourcePackets state and rendered in the Ingest source recent-packets strip. With that UI removed, the packet title has no surface to land on; the proposal title (`Skill: Artifact polish loop run`) is still asserted and remains the real signal that crystallize succeeded.
This commit is contained in:
parent
10e11531a1
commit
5f939ce601
3 changed files with 1 additions and 494 deletions
|
|
@ -4,15 +4,10 @@
|
|||
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import type {
|
||||
AutomationContentPacket,
|
||||
AutomationEvolutionProposal,
|
||||
AutomationEvolutionProposalListResponse,
|
||||
AutomationSourceIngestionResponse,
|
||||
AutomationSourceKind,
|
||||
AutomationSourcePacketListResponse,
|
||||
AutomationTemplate as ContractAutomationTemplate,
|
||||
AutomationTemplateListResponse,
|
||||
AutomationTokenCompressionMode,
|
||||
ConnectorDetail,
|
||||
Routine,
|
||||
RoutineRun,
|
||||
|
|
@ -162,41 +157,6 @@ const TEMPLATE_FILTERS: ReadonlyArray<{ id: TemplateFilter; label: string }> = [
|
|||
{ id: 'quality', label: 'Quality' },
|
||||
];
|
||||
|
||||
const SOURCE_KIND_OPTIONS: ReadonlyArray<{ id: AutomationSourceKind; label: string }> = [
|
||||
{ id: 'connector', label: 'Connector' },
|
||||
{ id: 'url', label: 'URL' },
|
||||
{ id: 'repo', label: 'Repo' },
|
||||
{ id: 'artifact', label: 'Artifact' },
|
||||
{ id: 'chat', label: 'Chat' },
|
||||
{ id: 'upload', label: 'Upload' },
|
||||
];
|
||||
|
||||
const COMPRESSION_OPTIONS: ReadonlyArray<{ id: AutomationTokenCompressionMode; label: string }> = [
|
||||
{ id: 'balanced', label: 'Balanced' },
|
||||
{ id: 'aggressive', label: 'Aggressive' },
|
||||
{ id: 'off', label: 'Off' },
|
||||
];
|
||||
|
||||
type SourceIngestionForm = {
|
||||
templateId: string;
|
||||
sourceKind: AutomationSourceKind;
|
||||
sourceRef: string;
|
||||
title: string;
|
||||
bodyMarkdown: string;
|
||||
connectorId: string;
|
||||
tokenCompression: AutomationTokenCompressionMode;
|
||||
};
|
||||
|
||||
const DEFAULT_SOURCE_FORM: SourceIngestionForm = {
|
||||
templateId: 'ingest-source-memory-tree',
|
||||
sourceKind: 'connector',
|
||||
sourceRef: '',
|
||||
title: '',
|
||||
bodyMarkdown: '',
|
||||
connectorId: '',
|
||||
tokenCompression: 'balanced',
|
||||
};
|
||||
|
||||
function scheduleStatusLabel(routine: Routine): string {
|
||||
if (!routine.enabled) return 'Paused';
|
||||
return describeScheduleSummary(routine.schedule);
|
||||
|
|
@ -405,10 +365,7 @@ export function TasksView({ skills = [], designTemplates = [], connectors = [] }
|
|||
const [templateFilter, setTemplateFilter] = useState<TemplateFilter>('all');
|
||||
const [automationCatalog, setAutomationCatalog] = useState<ContractAutomationTemplate[]>([]);
|
||||
const [proposals, setProposals] = useState<AutomationEvolutionProposal[]>([]);
|
||||
const [sourcePackets, setSourcePackets] = useState<AutomationContentPacket[]>([]);
|
||||
const [sourceForm, setSourceForm] = useState<SourceIngestionForm>(DEFAULT_SOURCE_FORM);
|
||||
const [proposalBusyId, setProposalBusyId] = useState<string | null>(null);
|
||||
const [ingestingSource, setIngestingSource] = useState(false);
|
||||
const [crystallizingRunId, setCrystallizingRunId] = useState<string | null>(null);
|
||||
const [expandedId, setExpandedId] = useState<string | null>(null);
|
||||
const [historyTick, setHistoryTick] = useState(0);
|
||||
|
|
@ -436,18 +393,11 @@ export function TasksView({ skills = [], designTemplates = [], connectors = [] }
|
|||
return (await res.json()) as AutomationEvolutionProposalListResponse;
|
||||
})
|
||||
.catch(() => null);
|
||||
const sourcePacketRequest = fetch('/api/automation-source-packets?limit=3')
|
||||
.then(async (res) => {
|
||||
if (!res.ok) return null;
|
||||
return (await res.json()) as AutomationSourcePacketListResponse;
|
||||
})
|
||||
.catch(() => null);
|
||||
const [rRes, pRes, tJson, proposalJson, sourcePacketJson] = await Promise.all([
|
||||
const [rRes, pRes, tJson, proposalJson] = await Promise.all([
|
||||
fetch('/api/routines'),
|
||||
fetch('/api/projects'),
|
||||
templateRequest,
|
||||
proposalRequest,
|
||||
sourcePacketRequest,
|
||||
]);
|
||||
if (!rRes.ok) throw new Error(`routines: ${rRes.status}`);
|
||||
const rJson = await rRes.json();
|
||||
|
|
@ -467,9 +417,6 @@ export function TasksView({ skills = [], designTemplates = [], connectors = [] }
|
|||
if (proposalJson) {
|
||||
setProposals(Array.isArray(proposalJson.proposals) ? proposalJson.proposals : []);
|
||||
}
|
||||
if (sourcePacketJson) {
|
||||
setSourcePackets(Array.isArray(sourcePacketJson.packets) ? sourcePacketJson.packets : []);
|
||||
}
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
|
|
@ -490,61 +437,6 @@ export function TasksView({ skills = [], designTemplates = [], connectors = [] }
|
|||
|
||||
const activeCount = routines.filter((routine) => routine.enabled).length;
|
||||
const pausedCount = routines.length - activeCount;
|
||||
const sourceIngestionTemplates = useMemo(
|
||||
() =>
|
||||
automationCatalog.filter((template) =>
|
||||
template.stages.some((stage) => stage.kind === 'ingest' || stage.kind === 'propose'),
|
||||
),
|
||||
[automationCatalog],
|
||||
);
|
||||
|
||||
const patchSourceForm = (patch: Partial<SourceIngestionForm>) => {
|
||||
setSourceForm((current) => ({ ...current, ...patch }));
|
||||
};
|
||||
|
||||
const submitSourceIngestion = async () => {
|
||||
if (!sourceForm.bodyMarkdown.trim()) {
|
||||
setError('Paste source content before ingesting it.');
|
||||
return;
|
||||
}
|
||||
setIngestingSource(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await fetch('/api/automation-ingestions', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
templateId: sourceForm.templateId || undefined,
|
||||
sourceKind: sourceForm.sourceKind,
|
||||
sourceRef: sourceForm.sourceRef || undefined,
|
||||
title: sourceForm.title || undefined,
|
||||
bodyMarkdown: sourceForm.bodyMarkdown,
|
||||
connectorId:
|
||||
sourceForm.sourceKind === 'connector' && sourceForm.connectorId
|
||||
? sourceForm.connectorId
|
||||
: undefined,
|
||||
tokenCompression: sourceForm.tokenCompression,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const j = await res.json().catch(() => ({}));
|
||||
throw new Error(j.error || `ingestion failed: ${res.status}`);
|
||||
}
|
||||
const json = (await res.json()) as AutomationSourceIngestionResponse;
|
||||
setSourcePackets((current) => [json.packet, ...current].slice(0, 3));
|
||||
setSourceForm((current) => ({
|
||||
...current,
|
||||
title: '',
|
||||
sourceRef: '',
|
||||
bodyMarkdown: '',
|
||||
}));
|
||||
await refresh();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
} finally {
|
||||
setIngestingSource(false);
|
||||
}
|
||||
};
|
||||
|
||||
const reviewProposal = async (id: string, action: 'apply' | 'reject') => {
|
||||
setProposalBusyId(id);
|
||||
|
|
@ -607,8 +499,6 @@ export function TasksView({ skills = [], designTemplates = [], connectors = [] }
|
|||
const j = await res.json().catch(() => ({}));
|
||||
throw new Error(j.error || `crystallize failed: ${res.status}`);
|
||||
}
|
||||
const json = (await res.json()) as RoutineRunCrystallizeResponse;
|
||||
setSourcePackets((current) => [json.packet, ...current].slice(0, 3));
|
||||
await refresh();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
|
|
@ -897,138 +787,6 @@ export function TasksView({ skills = [], designTemplates = [], connectors = [] }
|
|||
</section>
|
||||
) : null}
|
||||
|
||||
<section className="automations-ingest" aria-label="Source ingestion">
|
||||
<div className="automations-section-head">
|
||||
<div>
|
||||
<h2 className="automations-section__label">Ingest source</h2>
|
||||
<p className="automations-section__sub">
|
||||
Turn connector, repo, artifact, or chat context into reviewable evolution proposals.
|
||||
</p>
|
||||
</div>
|
||||
<span className="automations-section__meta">{sourcePackets.length} recent</span>
|
||||
</div>
|
||||
<div className="automation-ingest-panel">
|
||||
<div className="automation-ingest-controls">
|
||||
<label className="automation-ingest-field">
|
||||
<span>Template</span>
|
||||
<select
|
||||
value={sourceForm.templateId}
|
||||
onChange={(event) => patchSourceForm({ templateId: event.currentTarget.value })}
|
||||
>
|
||||
{sourceIngestionTemplates.length === 0 ? (
|
||||
<option value={sourceForm.templateId}>{sourceForm.templateId}</option>
|
||||
) : null}
|
||||
{sourceIngestionTemplates.map((template) => (
|
||||
<option key={template.id} value={template.id}>
|
||||
{template.title}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="automation-ingest-field">
|
||||
<span>Source</span>
|
||||
<select
|
||||
value={sourceForm.sourceKind}
|
||||
onChange={(event) =>
|
||||
patchSourceForm({ sourceKind: event.currentTarget.value as AutomationSourceKind })
|
||||
}
|
||||
>
|
||||
{SOURCE_KIND_OPTIONS.map((option) => (
|
||||
<option key={option.id} value={option.id}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="automation-ingest-field">
|
||||
<span>Compression</span>
|
||||
<select
|
||||
value={sourceForm.tokenCompression}
|
||||
onChange={(event) =>
|
||||
patchSourceForm({
|
||||
tokenCompression: event.currentTarget.value as AutomationTokenCompressionMode,
|
||||
})
|
||||
}
|
||||
>
|
||||
{COMPRESSION_OPTIONS.map((option) => (
|
||||
<option key={option.id} value={option.id}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
{sourceForm.sourceKind === 'connector' ? (
|
||||
<label className="automation-ingest-field">
|
||||
<span>Connector</span>
|
||||
<select
|
||||
value={sourceForm.connectorId}
|
||||
onChange={(event) => patchSourceForm({ connectorId: event.currentTarget.value })}
|
||||
>
|
||||
<option value="">Any connected source</option>
|
||||
{connectors.map((connector) => (
|
||||
<option key={connector.id} value={connector.id}>
|
||||
{connector.name}
|
||||
{connector.accountLabel ? ` · ${connector.accountLabel}` : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="automation-ingest-fields">
|
||||
<label className="automation-ingest-field">
|
||||
<span>Title</span>
|
||||
<input
|
||||
value={sourceForm.title}
|
||||
onChange={(event) => patchSourceForm({ title: event.currentTarget.value })}
|
||||
placeholder="Decision, brand notes, workflow pattern..."
|
||||
/>
|
||||
</label>
|
||||
<label className="automation-ingest-field">
|
||||
<span>Source ref</span>
|
||||
<input
|
||||
value={sourceForm.sourceRef}
|
||||
onChange={(event) => patchSourceForm({ sourceRef: event.currentTarget.value })}
|
||||
placeholder="URL, repo path, connector event id, artifact id..."
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<label className="automation-ingest-field automation-ingest-field--body">
|
||||
<span>Content</span>
|
||||
<textarea
|
||||
value={sourceForm.bodyMarkdown}
|
||||
onChange={(event) => patchSourceForm({ bodyMarkdown: event.currentTarget.value })}
|
||||
placeholder="Paste the content to canonicalize into a source packet and proposals."
|
||||
/>
|
||||
</label>
|
||||
<div className="automation-ingest-footer">
|
||||
{sourcePackets.length > 0 ? (
|
||||
<ul className="automation-ingest-recent" aria-label="Recent source packets">
|
||||
{sourcePackets.map((packet) => (
|
||||
<li key={packet.id}>
|
||||
<span>{packet.title}</span>
|
||||
<small>
|
||||
{packet.sourceKind} · {packet.tokenStats.originalTokens} tokens
|
||||
</small>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<span className="automation-ingest-empty">No source packets yet.</span>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="automations-view__new"
|
||||
onClick={submitSourceIngestion}
|
||||
disabled={ingestingSource}
|
||||
>
|
||||
<Icon name="sparkles" size={14} />
|
||||
<span>{ingestingSource ? 'Ingesting' : 'Ingest'}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="automations-templates" aria-label="Automation templates">
|
||||
<div className="automations-templates__head">
|
||||
<div className="automations-templates__head-copy">
|
||||
|
|
|
|||
|
|
@ -567,139 +567,6 @@
|
|||
cursor: progress;
|
||||
}
|
||||
|
||||
/* ---------- Source ingestion ---------- */
|
||||
|
||||
.automations-ingest {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.automation-ingest-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-panel);
|
||||
box-shadow: var(--shadow-xs);
|
||||
}
|
||||
|
||||
.automation-ingest-controls,
|
||||
.automation-ingest-fields {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.automation-ingest-controls {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.automation-ingest-fields {
|
||||
grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
|
||||
}
|
||||
|
||||
.automation-ingest-field {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.automation-ingest-field input,
|
||||
.automation-ingest-field select,
|
||||
.automation-ingest-field textarea {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background-color: var(--bg-subtle);
|
||||
color: var(--text);
|
||||
font: 12.5px/1.4 var(--font, system-ui);
|
||||
}
|
||||
|
||||
.automation-ingest-field input,
|
||||
.automation-ingest-field select {
|
||||
height: 34px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.automation-ingest-field textarea {
|
||||
min-height: 116px;
|
||||
padding: 10px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.automation-ingest-field input:focus,
|
||||
.automation-ingest-field select:focus,
|
||||
.automation-ingest-field textarea:focus {
|
||||
outline: none;
|
||||
border-color: color-mix(in srgb, var(--accent, #79a8ff) 44%, var(--border));
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, #79a8ff) 14%, transparent);
|
||||
}
|
||||
|
||||
.automation-ingest-field--body {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.automation-ingest-field--body > span {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.automation-ingest-footer {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 12px;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.automation-ingest-recent {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.automation-ingest-recent li {
|
||||
display: flex;
|
||||
max-width: 260px;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding: 7px 9px;
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: 8px;
|
||||
background: color-mix(in srgb, var(--bg-subtle) 72%, transparent);
|
||||
}
|
||||
|
||||
.automation-ingest-recent span,
|
||||
.automation-ingest-recent small {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.automation-ingest-recent span {
|
||||
color: var(--text);
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.automation-ingest-recent small,
|
||||
.automation-ingest-empty {
|
||||
color: var(--text-muted);
|
||||
font-size: 11.5px;
|
||||
}
|
||||
|
||||
/* ---------- Template gallery ---------- */
|
||||
|
||||
.automations-templates {
|
||||
|
|
@ -1676,10 +1543,6 @@
|
|||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.automation-ingest-controls {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.automations-templates__grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
|
@ -1705,12 +1568,6 @@
|
|||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.automation-ingest-controls,
|
||||
.automation-ingest-fields,
|
||||
.automation-ingest-footer {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.automations-templates__grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,113 +162,6 @@ describe('TasksView automation templates', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('ingests pasted source content into source packets and proposals', async () => {
|
||||
const postBodies: unknown[] = [];
|
||||
let proposals: AutomationEvolutionProposal[] = [];
|
||||
let packets = [] as Array<{
|
||||
id: string;
|
||||
sourceKind: string;
|
||||
title: string;
|
||||
capturedAt: string;
|
||||
tokenStats: { originalTokens: number };
|
||||
}>;
|
||||
globalThis.fetch = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const url = input.toString();
|
||||
if (url === '/api/routines' && (!init || init.method === undefined)) {
|
||||
return new Response(JSON.stringify({ routines: [] }), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
if (url === '/api/projects' && (!init || init.method === undefined)) {
|
||||
return new Response(JSON.stringify({ projects: [] }), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
if (url === '/api/automation-templates' && (!init || init.method === undefined)) {
|
||||
return new Response(JSON.stringify({ templates: [daemonTemplate] }), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
if (url === '/api/automation-proposals?status=pending-review' && (!init || init.method === undefined)) {
|
||||
return new Response(JSON.stringify({ proposals }), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
if (url === '/api/automation-source-packets?limit=3' && (!init || init.method === undefined)) {
|
||||
return new Response(JSON.stringify({ packets }), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
if (url === '/api/automation-ingestions' && init?.method === 'POST') {
|
||||
postBodies.push(JSON.parse(String(init.body)));
|
||||
const packet = {
|
||||
id: 'packet-1',
|
||||
sourceKind: 'repo',
|
||||
sourceRef: 'https://github.com/acme/design',
|
||||
title: 'Acme source',
|
||||
capturedAt: '2026-05-18T00:00:00.000Z',
|
||||
bodyMarkdown: 'Primary color #335CFF',
|
||||
provenance: [],
|
||||
attachments: [],
|
||||
sensitivity: 'workspace',
|
||||
capabilityHints: [],
|
||||
tokenStats: { originalTokens: 6 },
|
||||
candidateSinks: ['memory', 'design-system'],
|
||||
};
|
||||
proposals = [{ ...memoryProposal, id: 'proposal-ingested-1', title: 'Memory: Acme source' }];
|
||||
packets = [packet];
|
||||
return new Response(JSON.stringify({
|
||||
packet,
|
||||
compressionReport: {
|
||||
mode: 'balanced',
|
||||
status: 'skipped',
|
||||
beforeTokens: 6,
|
||||
afterTokens: 6,
|
||||
summary: 'Already compact',
|
||||
preservedSourcePacketId: 'packet-1',
|
||||
},
|
||||
proposals,
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
return new Response(JSON.stringify({}), { status: 404 });
|
||||
}) as typeof fetch;
|
||||
|
||||
render(<TasksView />);
|
||||
|
||||
await screen.findByText('Ingest source');
|
||||
fireEvent.change(screen.getByLabelText('Title'), {
|
||||
target: { value: 'Acme source' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText('Source ref'), {
|
||||
target: { value: 'https://github.com/acme/design' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText('Content'), {
|
||||
target: { value: 'Primary color #335CFF' },
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: /^Ingest$/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(postBodies).toHaveLength(1);
|
||||
expect(postBodies[0]).toMatchObject({
|
||||
templateId: 'ingest-source-memory-tree',
|
||||
sourceKind: 'connector',
|
||||
title: 'Acme source',
|
||||
sourceRef: 'https://github.com/acme/design',
|
||||
bodyMarkdown: 'Primary color #335CFF',
|
||||
});
|
||||
expect(screen.getByText('Memory: Acme source')).toBeTruthy();
|
||||
expect(screen.getByText('Acme source')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('crystallizes a successful automation run into reviewable proposals', async () => {
|
||||
const crystallizeCalls: string[] = [];
|
||||
let proposals: AutomationEvolutionProposal[] = [];
|
||||
|
|
@ -404,7 +297,6 @@ describe('TasksView automation templates', () => {
|
|||
'/api/routines/routine-1/runs/run-succeeded-1/crystallize',
|
||||
]);
|
||||
expect(screen.getByText('Skill: Artifact polish loop run')).toBeTruthy();
|
||||
expect(screen.getByText('Artifact polish loop run')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue