Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
3e86d54
feat(task T01): implement via codex
Jul 12, 2026
2078777
Merge pull request #2950 from codeux-ai/task/feature-codux-21-t01-cod…
numnx Jul 12, 2026
83fa6f6
feat(task T02): implement via codex
Jul 12, 2026
72954f0
Merge pull request #2952 from codeux-ai/task/feature-codux-21-t02-cod…
numnx Jul 12, 2026
27faf92
feat(task T03): implement via codex
Jul 12, 2026
81a423e
Merge pull request #2953 from codeux-ai/task/feature-codux-21-t03-cod…
numnx Jul 12, 2026
d53fe2f
feat(task T04): implement via codex
Jul 12, 2026
b7e3c49
Merge pull request #2955 from codeux-ai/task/feature-codux-21-t04-cod…
numnx Jul 12, 2026
4b389d6
feat(task T05): implement via codex
Jul 12, 2026
5ddb546
Merge pull request #2956 from codeux-ai/task/feature-codux-21-t05-cod…
numnx Jul 12, 2026
aaa85a0
feat(task T06): implement via codex
Jul 12, 2026
4779cdd
Merge pull request #2958 from codeux-ai/task/feature-codux-21-t06-cod…
numnx Jul 12, 2026
c68c501
feat(task T07): implement via codex
Jul 12, 2026
3f7ffd3
Merge pull request #2959 from codeux-ai/task/feature-codux-21-t07-cod…
numnx Jul 12, 2026
baa834e
feat(task T08): implement via codex
Jul 12, 2026
f61d4dc
fix(ci): resolve failing checks on task/feature-codux-21-t08-codex-6b…
Jul 12, 2026
cff43ad
Merge pull request #2960 from codeux-ai/task/feature-codux-21-t08-cod…
numnx Jul 12, 2026
df9779c
feat(task T13): implement via codex
Jul 12, 2026
da9c405
feat(task T09): implement via codex
Jul 12, 2026
a06659d
feat(task T12): implement via codex
Jul 12, 2026
1137b81
Merge pull request #2961 from codeux-ai/task/feature-codux-21-t13-cod…
numnx Jul 12, 2026
59a3300
Merge pull request #2962 from codeux-ai/task/feature-codux-21-t09-cod…
numnx Jul 12, 2026
14b03f9
Merge pull request #2963 from codeux-ai/task/feature-codux-21-t12-cod…
numnx Jul 12, 2026
686d417
feat(task T10): implement via codex
Jul 12, 2026
2b93362
feat(task T11): implement via codex
Jul 12, 2026
2a030df
Merge pull request #2964 from codeux-ai/task/feature-codux-21-t10-cod…
numnx Jul 12, 2026
4febcc1
fix(merge): resolve feature/CODUX-216-nodes-full-integration into tas…
Jul 12, 2026
e37aa28
Merge pull request #2965 from codeux-ai/task/feature-codux-21-t11-cod…
numnx Jul 12, 2026
e196a61
feat(task T15): implement via codex
Jul 12, 2026
b3abdf1
feat(task T14): implement via codex
Jul 12, 2026
3fff649
Merge pull request #2967 from codeux-ai/task/feature-codux-21-t14-cod…
numnx Jul 12, 2026
674e272
fix(ci): resolve failing checks on task/feature-codux-21-t15-codex-cc…
Jul 12, 2026
4ee6705
Merge pull request #2966 from codeux-ai/task/feature-codux-21-t15-cod…
numnx Jul 12, 2026
1030768
feat(task T21): implement via codex
Jul 12, 2026
4492823
feat(task T18): implement via codex
Jul 12, 2026
aff2b30
Merge pull request #2969 from codeux-ai/task/feature-codux-21-t21-cod…
numnx Jul 12, 2026
fcb7331
feat(task T17): implement via codex
Jul 12, 2026
2b1be27
feat(task T16): implement via codex
Jul 12, 2026
d5f8069
Merge pull request #2970 from codeux-ai/task/feature-codux-21-t18-cod…
numnx Jul 12, 2026
ac0991b
feat(task T20): implement via codex
Jul 12, 2026
e4bb163
feat(task T19): implement via codex
Jul 12, 2026
80ccbbc
Merge pull request #2972 from codeux-ai/task/feature-codux-21-t16-cod…
numnx Jul 12, 2026
e76d237
fix(merge): resolve feature/CODUX-216-nodes-full-integration into tas…
Jul 12, 2026
00d742e
Merge pull request #2971 from codeux-ai/task/feature-codux-21-t17-cod…
numnx Jul 12, 2026
d5b64b7
Merge pull request #2974 from codeux-ai/task/feature-codux-21-t19-cod…
numnx Jul 12, 2026
d616136
fix(merge): resolve feature/CODUX-216-nodes-full-integration into tas…
Jul 12, 2026
91773c0
Merge pull request #2973 from codeux-ai/task/feature-codux-21-t20-cod…
numnx Jul 12, 2026
2967be7
feat(task T22): implement via codex
Jul 12, 2026
cbd6df1
Merge pull request #2975 from codeux-ai/task/feature-codux-21-t22-cod…
numnx Jul 12, 2026
20a38eb
feat(task T23): implement via codex
Jul 12, 2026
c4e2e0d
Merge pull request #2976 from codeux-ai/task/feature-codux-21-t23-cod…
numnx Jul 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .code-ux/nodes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Custom node packages

Code UX generates each project-owned TypeScript node under `.code-ux/nodes/<node-id>/`.
Packages are drafts until container validation passes and an immutable artifact is explicitly published.
Generated source is never imported or executed by the Code UX server process.
552 changes: 201 additions & 351 deletions dashboard/src/v2/NodesPage.tsx

Large diffs are not rendered by default.

64 changes: 48 additions & 16 deletions dashboard/src/v2/components/nodes/NodeFlowInspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import type {
NodeFlowNode,
NodeFlowSkillAttachment,
NodeFlowValidationResponse,
NodeDefinitionManifest,
NodeFlowRequiredCredential,
} from "../../types.js";
import {
applyWidgetDefaults,
Expand All @@ -21,10 +23,16 @@ interface NodeFlowInspectorProps {
attachments: NodeFlowSkillAttachment[];
attachAgentId: string;
attaching?: boolean;
attachmentsLoading?: boolean;
attachmentError?: string | null;
onAttachAgentIdChange: (agentPresetId: string) => void;
onAttachAgent: () => void;
onDetachAgent: (agentPresetId: string) => void;
onRetryAttachments?: () => void;
onNodeChange: (nodeId: string, update: Partial<NodeFlowNode>) => void;
definition?: NodeDefinitionManifest | null;
requiredCredentials?: NodeFlowRequiredCredential[];
onRequestCredential?: (nodeId: string, slot: string) => void;
}

const inputClass = "w-full rounded-xl border border-black/[0.08] bg-white/75 px-3 py-2 text-sm text-slate-800 shadow-sm outline-none transition focus:border-signal-500/50 focus:ring-2 focus:ring-signal-500/20 dark:border-white/[0.08] dark:bg-white/[0.04] dark:text-slate-100";
Expand All @@ -36,10 +44,16 @@ export const NodeFlowInspector: FunctionComponent<NodeFlowInspectorProps> = ({
attachments,
attachAgentId,
attaching = false,
attachmentsLoading = false,
attachmentError = null,
onAttachAgentIdChange,
onAttachAgent,
onDetachAgent,
onRetryAttachments,
onNodeChange,
definition = null,
requiredCredentials = [],
onRequestCredential,
}) => {
const messagesByField = buildValidationMessagesByField(validation);

Expand All @@ -51,7 +65,8 @@ export const NodeFlowInspector: FunctionComponent<NodeFlowInspectorProps> = ({
);
}

const data = applyWidgetDefaults(selectedNode.widgetSchema, selectedNode.data);
const widgetSchema = definition?.ui.widgetSchema ?? selectedNode.widgetSchema;
const data = applyWidgetDefaults(widgetSchema, selectedNode.data);

const updateDataField = (fieldId: string, value: NodeFlowJsonValue): void => {
onNodeChange(selectedNode.id, {
Expand Down Expand Up @@ -79,14 +94,11 @@ export const NodeFlowInspector: FunctionComponent<NodeFlowInspectorProps> = ({
onInput={(event) => onNodeChange(selectedNode.id, { title: event.currentTarget.value })}
/>
</label>
<label className="flex flex-col gap-1.5 text-xs font-bold uppercase tracking-[0.14em] text-slate-500 dark:text-slate-400">
Type
<input
className={inputClass}
value={selectedNode.type}
onInput={(event) => onNodeChange(selectedNode.id, { type: event.currentTarget.value })}
/>
</label>
<div className="rounded-xl border border-black/[0.06] bg-white/55 p-3 text-xs text-slate-500 dark:border-white/[0.06] dark:bg-white/[0.03]">
<p className="font-bold text-slate-800 dark:text-slate-100">{definition?.ui.label ?? selectedNode.type} Β· v{definition?.version ?? selectedNode.definition?.version ?? 1}</p>
<p className="mt-1">{definition?.ports.length ?? selectedNode.ports?.length ?? 0} typed ports Β· {definition?.sideEffect ?? selectedNode.sideEffect ?? "none"} side effects</p>
{(definition?.capabilities ?? selectedNode.capabilities ?? []).length ? <p className="mt-1 break-words">Capabilities: {(definition?.capabilities ?? selectedNode.capabilities ?? []).join(", ")}</p> : null}
</div>
<label className="flex flex-col gap-1.5 text-xs font-bold uppercase tracking-[0.14em] text-slate-500 dark:text-slate-400">
Description
<textarea
Expand All @@ -99,12 +111,12 @@ export const NodeFlowInspector: FunctionComponent<NodeFlowInspectorProps> = ({

<section className="flex flex-col gap-4 border-t border-black/[0.06] pt-4 dark:border-white/[0.06]" aria-labelledby="node-widgets-heading">
<h3 id="node-widgets-heading" className="text-xs font-bold uppercase tracking-[0.16em] text-slate-500 dark:text-slate-400">Widgets</h3>
{(selectedNode.widgetSchema?.fields.length ?? 0) === 0 ? (
{(widgetSchema?.fields.length ?? 0) === 0 ? (
<p className="rounded-xl border border-black/[0.06] bg-white/55 px-3 py-3 text-sm text-slate-500 dark:border-white/[0.06] dark:bg-white/[0.03]">
No widgets configured for this node.
</p>
) : (
selectedNode.widgetSchema!.fields.map((field) => (
widgetSchema!.fields.map((field) => (
<NodeWidgetField
key={field.id}
field={field}
Expand All @@ -116,13 +128,25 @@ export const NodeFlowInspector: FunctionComponent<NodeFlowInspectorProps> = ({
)}
</section>

<section className="flex flex-col gap-3 border-t border-black/[0.06] pt-4 dark:border-white/[0.06]" aria-labelledby="node-agent-attachments-heading">
<section className="flex flex-col gap-3 border-t border-black/[0.06] pt-4 dark:border-white/[0.06]" aria-labelledby="node-credentials-heading">
<h3 id="node-credentials-heading" className="text-xs font-bold uppercase tracking-[0.16em] text-slate-500 dark:text-slate-400">Credential bindings</h3>
{requiredCredentials.length === 0 ? <p className="text-xs text-slate-500">This node does not request credentials.</p> : requiredCredentials.map((credential) => (
<div key={credential.slot} className="rounded-xl border border-black/[0.06] bg-white/60 p-3 dark:border-white/[0.06] dark:bg-white/[0.03]">
<div className="flex items-center justify-between gap-3"><span className="text-sm font-bold text-slate-800 dark:text-slate-100">{credential.slot}</span><span className={`text-[10px] font-bold uppercase ${credential.status === "bound" ? "text-status-green" : "text-status-red"}`}>{credential.status}</span></div>
<p className="mt-1 text-xs text-slate-500">{credential.allowedKinds.join(", ")} Β· secret value never displayed</p>
{credential.status !== "bound" && onRequestCredential ? <button type="button" className="mt-2 rounded-lg border border-signal-500/30 px-2.5 py-1.5 text-xs font-bold text-signal-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-signal-500/40" onClick={() => onRequestCredential(selectedNode.id, credential.slot)}>Request binding</button> : null}
</div>
))}
</section>

<section className="flex flex-col gap-3 border-t border-black/[0.06] pt-4 dark:border-white/[0.06]" aria-labelledby="node-agent-attachments-heading" aria-busy={attachmentsLoading || attaching}>
<h3 id="node-agent-attachments-heading" className="text-xs font-bold uppercase tracking-[0.16em] text-slate-500 dark:text-slate-400">Agent Attachments</h3>
<div className="flex gap-2">
<select
aria-label="Agent preset"
className={inputClass}
value={attachAgentId}
disabled={attachmentsLoading || attaching}
onChange={(event) => onAttachAgentIdChange(event.currentTarget.value)}
>
<option value="">Select agent</option>
Expand All @@ -133,16 +157,23 @@ export const NodeFlowInspector: FunctionComponent<NodeFlowInspectorProps> = ({
<button
type="button"
aria-label="Attach node flow to agent"
disabled={!attachAgentId || attaching}
disabled={!attachAgentId || attachmentsLoading || attaching}
onClick={onAttachAgent}
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-signal-500 text-white transition hover:bg-signal-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-signal-500/40 disabled:opacity-50 dark:text-void-900"
>
<Link2 className="h-4 w-4" aria-hidden="true" />
</button>
</div>
{attachments.length === 0 ? (
{attachmentsLoading ? <p role="status" className="text-xs leading-relaxed text-slate-500 dark:text-slate-400">Loading agent attachments…</p> : null}
{attachmentError ? (
<div role="alert" className="rounded-xl border border-status-red/20 bg-status-red/[0.06] p-3 text-xs text-status-red">
<p>{attachmentError}</p>
{onRetryAttachments ? <button type="button" className="mt-2 font-bold underline focus:outline-none focus-visible:ring-2 focus-visible:ring-signal-500/40" onClick={onRetryAttachments}>Retry attachments</button> : null}
</div>
) : null}
{!attachmentsLoading && !attachmentError && attachments.length === 0 ? (
<p className="text-xs leading-relaxed text-slate-500 dark:text-slate-400">No agents attached.</p>
) : (
) : !attachmentsLoading && !attachmentError ? (
<div className="flex flex-col gap-2">
{attachments.map((attachment) => {
const agent = agents.find((entry) => entry.id === attachment.agentPresetId);
Expand All @@ -155,6 +186,7 @@ export const NodeFlowInspector: FunctionComponent<NodeFlowInspectorProps> = ({
<button
type="button"
aria-label={`Detach ${agent?.name ?? attachment.agentPresetId}`}
disabled={attaching}
className="flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 transition hover:bg-status-red/[0.08] hover:text-status-red focus:outline-none focus-visible:ring-2 focus-visible:ring-signal-500/40"
onClick={() => onDetachAgent(attachment.agentPresetId)}
>
Expand All @@ -164,7 +196,7 @@ export const NodeFlowInspector: FunctionComponent<NodeFlowInspectorProps> = ({
);
})}
</div>
)}
) : null}
</section>
</aside>
);
Expand Down
30 changes: 30 additions & 0 deletions dashboard/src/v2/components/nodes/NodeGovernancePanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { FunctionComponent } from "preact";
import { CheckCircle2, FlaskConical, GitCompare, RotateCcw, ShieldAlert, UploadCloud } from "lucide-preact";
import type { NodeFlowDraftReview } from "../../types.js";
import type { NodeFlowDryRunResponse, NodeFlowVersionDiff } from "../../lib/node-flow-api.js";
import { Button } from "../ui/Button.js";

interface NodeGovernancePanelProps {
review: NodeFlowDraftReview | null;
dryRun: NodeFlowDryRunResponse | null;
diff: NodeFlowVersionDiff | null;
busy?: boolean;
onValidate: () => void;
onDryRun: () => void;
onCompare: () => void;
onPublish: () => void;
onRollback: () => void;
}

export const NodeGovernancePanel: FunctionComponent<NodeGovernancePanelProps> = ({ review, dryRun, diff, busy, onValidate, onDryRun, onCompare, onPublish, onRollback }) => (
<section className="rounded-[var(--radius-panel)] border border-black/[0.06] bg-white/70 p-4 shadow-[var(--elevation-soft)] dark:border-white/[0.06] dark:bg-white/[0.035]" aria-labelledby="governance-heading">
<div className="flex flex-wrap items-start justify-between gap-3"><div><p className="text-[10px] font-bold uppercase tracking-[0.18em] text-signal-600 dark:text-signal-400">T06 control plane</p><h2 id="governance-heading" className="text-base font-bold text-slate-900 dark:text-white">Validation, policy & publication</h2></div><div className="flex flex-wrap gap-2"><Button size="sm" variant="secondary" icon={CheckCircle2} onClick={onValidate} disabled={busy}>Validate</Button><Button size="sm" variant="secondary" icon={FlaskConical} onClick={onDryRun} disabled={busy}>Dry run</Button><Button size="sm" variant="secondary" icon={GitCompare} onClick={onCompare} disabled={busy || !review?.publishedVersion}>Compare</Button><Button size="sm" variant="ghost" icon={RotateCcw} onClick={onRollback} disabled={busy || !review?.publishedVersion}>Rollback</Button><Button size="sm" icon={UploadCloud} onClick={onPublish} disabled={busy || !review?.valid}>Publish</Button></div></div>
{!review ? <p role="status" className="mt-4 text-sm text-slate-500">Validate the draft to load its governed review.</p> : <div className="mt-4 grid gap-3 lg:grid-cols-3">
<div className="rounded-xl border border-black/[0.06] p-3 dark:border-white/[0.06]"><p className="text-xs font-bold uppercase text-slate-400">Revision</p><p className="mt-1 text-sm font-bold text-slate-900 dark:text-white">Draft r{review.draftRevision} Β· {review.publishedVersion ? `published v${review.publishedVersion}` : "unpublished"}</p><p className="mt-2 text-xs text-slate-500">{review.nodeCount} nodes Β· {review.edgeCount} edges</p></div>
<div className="rounded-xl border border-black/[0.06] p-3 dark:border-white/[0.06]"><p className="text-xs font-bold uppercase text-slate-400">Permissions</p><p className="mt-1 text-sm text-slate-700 dark:text-slate-200">{review.requestedCapabilities.length ? review.requestedCapabilities.join(", ") : "No capabilities requested"}</p>{review.sideEffectDiffs.map((item) => <p key={item.nodeId} className="mt-1 text-xs text-amber-600">{item.description}</p>)}</div>
<div className="rounded-xl border border-black/[0.06] p-3 dark:border-white/[0.06]"><p className="text-xs font-bold uppercase text-slate-400">Policy</p>{review.policyFindings.length ? review.policyFindings.map((finding) => <p key={`${finding.code}-${finding.nodeId ?? "graph"}`} className={`mt-1 text-xs ${finding.severity === "error" ? "text-status-red" : "text-amber-600"}`}><ShieldAlert className="mr-1 inline h-3 w-3" aria-hidden="true" />{finding.message}</p>) : <p className="mt-1 text-sm text-status-green">No policy findings</p>}</div>
</div>}
{dryRun ? <div role="status" className={`mt-3 rounded-xl border p-3 text-sm ${dryRun.status === "ready" ? "border-status-green/20 bg-status-green/[0.06]" : "border-amber-500/25 bg-amber-500/[0.06]"}`}><strong>Dry run {dryRun.status}.</strong> No nodes executed; reviewed {dryRun.result.inputKeys.length} input keys.</div> : null}
{diff ? <div className="mt-3 rounded-xl border border-black/[0.06] p-3 text-sm dark:border-white/[0.06]" aria-label="Publication diff"><strong>Graph diff v{diff.fromVersion} β†’ v{diff.toVersion}:</strong> {diff.addedNodeIds.length} added, {diff.removedNodeIds.length} removed; nodes {diff.nodeCount.from} β†’ {diff.nodeCount.to}. Permission/side-effect changes: {diff.sideEffectDiffs.length}.</div> : null}
</section>
);
Loading
Loading