Add ultracode toggle that runs a request as a native dynamic workflow
A new transient per-request toggle state (off→plan→ultracode→fusion→moa) that whitelists the CLI's Workflow + Task tools so Anthropic's native dynamic-workflow orchestration runs in gott's headless -p mode without a permission gate (the engine silently denies Workflow otherwise). Effort is raised to xhigh (the real ultracode equivalent; "ultracode" itself is not a valid effort value) without downgrading an explicit max. Only enabled for Claude models (z.ai/GLM has no workflow runtime) and not in plan mode. The frontend injects a "use a workflow" hint, like plan mode injects its own. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
5418dc9225
commit
d5854a3f4b
7 files changed
+59
-12
No files matched your search
@@ -292,7 +292,7 @@ public static class ChatEndpoint
|
|||||||
|
|
||||||
if (anthropic.IsCliAvailable)
|
if (anthropic.IsCliAvailable)
|
||||||
{
|
{
|
||||||
await anthropic.StreamChatViaCli(res, ctx.ModelId, ctx.SystemPrompt, messages, ctx.ChainId, workspace: ctx.Workspace, allowUserChoice: ctx.Agent?.AllowUserChoice ?? true, planMode: body.PlanMode, webSearch: !string.IsNullOrEmpty(ctx.WebSearchVersion), sessionId: sessionId, agentId: ctx.AgentId, remoteMgr: remoteMgr, unityMcpUrl: ctx.Session?.UnityMcpUrl, unityMcpToken: ctx.Session?.UnityMcpToken, useAutoContext: ctx.Agent?.UseAutoContext ?? true, effort: ctx.Effort, seedUsage: panelUsage, workCt: workCt, sseCt: sseCt);
|
await anthropic.StreamChatViaCli(res, ctx.ModelId, ctx.SystemPrompt, messages, ctx.ChainId, workspace: ctx.Workspace, allowUserChoice: ctx.Agent?.AllowUserChoice ?? true, planMode: body.PlanMode, ultracode: body.Ultracode, webSearch: !string.IsNullOrEmpty(ctx.WebSearchVersion), sessionId: sessionId, agentId: ctx.AgentId, remoteMgr: remoteMgr, unityMcpUrl: ctx.Session?.UnityMcpUrl, unityMcpToken: ctx.Session?.UnityMcpToken, useAutoContext: ctx.Agent?.UseAutoContext ?? true, effort: ctx.Effort, seedUsage: panelUsage, workCt: workCt, sseCt: sseCt);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -150,4 +150,12 @@ public class ChatRequest
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("planMode")]
|
[JsonPropertyName("planMode")]
|
||||||
public bool PlanMode { get; set; }
|
public bool PlanMode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wenn true: Ultracode aktiv. Die Anfrage wird als nativer dynamischer Workflow
|
||||||
|
/// (mehrere Subagenten) gefahren – das Workflow-Tool wird freigeschaltet, Effort = xhigh.
|
||||||
|
/// Nur für den Claude-CLI-Pfad relevant (z.ai/GLM und der API-Pfad haben kein Workflow-Runtime).
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("ultracode")]
|
||||||
|
public bool Ultracode { get; set; }
|
||||||
}
|
}
|
||||||
@@ -30,6 +30,7 @@ public partial class AnthropicService
|
|||||||
string workspace = null,
|
string workspace = null,
|
||||||
bool allowUserChoice = false,
|
bool allowUserChoice = false,
|
||||||
bool planMode = false,
|
bool planMode = false,
|
||||||
|
bool ultracode = false,
|
||||||
bool webSearch = false,
|
bool webSearch = false,
|
||||||
string sessionId = null,
|
string sessionId = null,
|
||||||
string agentId = null,
|
string agentId = null,
|
||||||
@@ -301,6 +302,7 @@ public partial class AnthropicService
|
|||||||
workspace: workspace,
|
workspace: workspace,
|
||||||
hasWorkspace: !string.IsNullOrEmpty(workspace),
|
hasWorkspace: !string.IsNullOrEmpty(workspace),
|
||||||
planMode: planMode,
|
planMode: planMode,
|
||||||
|
ultracode: ultracode,
|
||||||
webSearch: webSearch,
|
webSearch: webSearch,
|
||||||
mcpConfigPath: mcpConfigPath,
|
mcpConfigPath: mcpConfigPath,
|
||||||
hasUnityMcp: !string.IsNullOrEmpty(unityMcpUrl),
|
hasUnityMcp: !string.IsNullOrEmpty(unityMcpUrl),
|
||||||
|
|||||||
@@ -214,6 +214,7 @@ public class ClaudeCliService
|
|||||||
string workspace = null,
|
string workspace = null,
|
||||||
bool hasWorkspace = false,
|
bool hasWorkspace = false,
|
||||||
bool planMode = false,
|
bool planMode = false,
|
||||||
|
bool ultracode = false,
|
||||||
bool webSearch = false,
|
bool webSearch = false,
|
||||||
string mcpConfigPath = null,
|
string mcpConfigPath = null,
|
||||||
bool hasUnityMcp = false,
|
bool hasUnityMcp = false,
|
||||||
@@ -229,6 +230,12 @@ public class ClaudeCliService
|
|||||||
// read-only gedachter Member (oder ein regulärer Plan-Modus-User) trotzdem Dateien schreiben
|
// read-only gedachter Member (oder ein regulärer Plan-Modus-User) trotzdem Dateien schreiben
|
||||||
// könnte. 'plan' blockiert alle Schreib-/Edit-/Bash-Operationen auf Permission-Ebene.
|
// könnte. 'plan' blockiert alle Schreib-/Edit-/Bash-Operationen auf Permission-Ebene.
|
||||||
var permissionMode = planMode ? "plan" : "acceptEdits";
|
var permissionMode = planMode ? "plan" : "acceptEdits";
|
||||||
|
|
||||||
|
// Ultracode nutzt xhigh-Reasoning (das reale Äquivalent – "ultracode" selbst ist KEIN
|
||||||
|
// gültiger Effort-Wert der CLI, wird still ignoriert). Ein bereits gewähltes "max" wird
|
||||||
|
// nicht heruntergestuft.
|
||||||
|
var effortToUse = ultracode ? (effort == "max" ? "max" : "xhigh") : effort;
|
||||||
|
|
||||||
var args = new List<string>
|
var args = new List<string>
|
||||||
{
|
{
|
||||||
"-p",
|
"-p",
|
||||||
@@ -259,6 +266,16 @@ public class ClaudeCliService
|
|||||||
// Kein Workspace: Read für Temp-Dateien (z.B. gesendete Bilder)
|
// Kein Workspace: Read für Temp-Dateien (z.B. gesendete Bilder)
|
||||||
tools.Add("Read");
|
tools.Add("Read");
|
||||||
}
|
}
|
||||||
|
// Ultracode: die nativen dynamischen Workflows der Claude CLI zulassen. Ohne diese
|
||||||
|
// Freigabe lehnt die Permission-Engine das Workflow-Tool headless STILL ab (kein
|
||||||
|
// interaktiver Gate im -p-Modus) → der Workflow läuft nicht. Task = die Subagenten,
|
||||||
|
// die ein Workflow spawnt. Nur Claude (z.ai/GLM hat kein Workflow-Runtime), nicht im
|
||||||
|
// Plan-Modus (read-only).
|
||||||
|
if (ultracode && !planMode && !IsZaiModel(modelId))
|
||||||
|
{
|
||||||
|
tools.Add("Workflow");
|
||||||
|
tools.Add("Task");
|
||||||
|
}
|
||||||
if (webSearch)
|
if (webSearch)
|
||||||
{
|
{
|
||||||
// GLM/z.ai kann Anthropics server-seitige WebSearch/WebFetch nicht ausführen →
|
// GLM/z.ai kann Anthropics server-seitige WebSearch/WebFetch nicht ausführen →
|
||||||
@@ -337,7 +354,7 @@ public class ClaudeCliService
|
|||||||
args.Add(mcpConfigPath);
|
args.Add(mcpConfigPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await RunCliStreamingAsync(args, prompt, onEvent, cancellationToken, effort, workspace, modelId);
|
return await RunCliStreamingAsync(args, prompt, onEvent, cancellationToken, effortToUse, workspace, modelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<CliChatResult> RunCliStreamingAsync(
|
private async Task<CliChatResult> RunCliStreamingAsync(
|
||||||
|
|||||||
@@ -452,6 +452,11 @@
|
|||||||
background: color-mix(in srgb, var(--primary) 28%, transparent);
|
background: color-mix(in srgb, var(--primary) 28%, transparent);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
|
#planModeBtn.ultracode-active {
|
||||||
|
color: var(--info);
|
||||||
|
background: color-mix(in srgb, var(--info) 18%, transparent);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
/* Emoji Picker */
|
/* Emoji Picker */
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -60,9 +60,10 @@ function apiMe() { return _get('/api/auth/me', null); }
|
|||||||
|
|
||||||
// ── Chat API ──
|
// ── Chat API ──
|
||||||
|
|
||||||
function apiChat(content, sessionId, signal, planMode) {
|
function apiChat(content, sessionId, signal, planMode, ultracode) {
|
||||||
var payload = { content: content, sessionId: sessionId };
|
var payload = { content: content, sessionId: sessionId };
|
||||||
if (planMode) payload.planMode = true;
|
if (planMode) payload.planMode = true;
|
||||||
|
if (ultracode) payload.ultracode = true;
|
||||||
return fetch('/api/chat', { method: 'POST', headers: _jsonHeaders, body: JSON.stringify(payload), signal: signal });
|
return fetch('/api/chat', { method: 'POST', headers: _jsonHeaders, body: JSON.stringify(payload), signal: signal });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+23
-9
@@ -228,13 +228,14 @@ function handleStreamEvent(eventType, data) {
|
|||||||
//
|
//
|
||||||
// _planModeActive bleibt als Compat-Variable (sendMessage + modals.js lesen sie direkt).
|
// _planModeActive bleibt als Compat-Variable (sendMessage + modals.js lesen sie direkt).
|
||||||
|
|
||||||
let _compositeMode = 'off'; // 'off' | 'plan' | 'fusion' | 'moa'
|
let _compositeMode = 'off'; // 'off' | 'plan' | 'ultracode' | 'fusion' | 'moa'
|
||||||
let _compositeHasAgents = false;
|
let _compositeHasAgents = false;
|
||||||
|
|
||||||
// Compat-Variablen – werden durch cycleCompositeMode / initCompositeMode synchron gehalten
|
// Compat-Variablen – werden durch cycleCompositeMode / initCompositeMode synchron gehalten
|
||||||
let _planModeActive = false; // true genau wenn _compositeMode === 'plan'
|
let _planModeActive = false; // true genau wenn _compositeMode === 'plan'
|
||||||
|
let _ultracodeActive = false; // true genau wenn _compositeMode === 'ultracode' (transient, pro Request)
|
||||||
|
|
||||||
const _COMPOSITE_ORDER = ['off', 'plan', 'fusion', 'moa'];
|
const _COMPOSITE_ORDER = ['off', 'plan', 'ultracode', 'fusion', 'moa'];
|
||||||
|
|
||||||
// Serialisierte Modus-Persistenz. Schnelles Durchklicken (off→plan→fusion→moa) feuert sonst
|
// Serialisierte Modus-Persistenz. Schnelles Durchklicken (off→plan→fusion→moa) feuert sonst
|
||||||
// ÜBERLAPPENDE PATCHes, deren Commit-Reihenfolge nicht garantiert ist → der zuletzt committete
|
// ÜBERLAPPENDE PATCHes, deren Commit-Reihenfolge nicht garantiert ist → der zuletzt committete
|
||||||
@@ -247,14 +248,19 @@ let _modePersist = Promise.resolve();
|
|||||||
function _renderCompositeBtn() {
|
function _renderCompositeBtn() {
|
||||||
var btn = $('#planModeBtn');
|
var btn = $('#planModeBtn');
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
btn.classList.remove('plan-mode-active', 'panel-mode-fusion', 'panel-mode-moa');
|
btn.classList.remove('plan-mode-active', 'ultracode-active', 'panel-mode-fusion', 'panel-mode-moa');
|
||||||
var icon = btn.querySelector('i');
|
var icon = btn.querySelector('i');
|
||||||
var input = $('#input');
|
var input = $('#input');
|
||||||
if (_compositeMode === 'plan') {
|
if (_compositeMode === 'plan') {
|
||||||
btn.classList.add('plan-mode-active');
|
btn.classList.add('plan-mode-active');
|
||||||
if (icon) icon.className = 'fa fa-map';
|
if (icon) icon.className = 'fa fa-map';
|
||||||
btn.title = 'Plan-Modus aktiv · klicken → Fusion';
|
btn.title = 'Plan-Modus aktiv · klicken → Ultracode';
|
||||||
if (input) input.placeholder = 'Planen...';
|
if (input) input.placeholder = 'Planen...';
|
||||||
|
} else if (_compositeMode === 'ultracode') {
|
||||||
|
btn.classList.add('ultracode-active');
|
||||||
|
if (icon) icon.className = 'fa fa-sitemap';
|
||||||
|
btn.title = 'Ultracode aktiv (dynamischer Workflow) · klicken → Fusion';
|
||||||
|
if (input) input.placeholder = 'Ultracode – als Workflow...';
|
||||||
} else if (_compositeMode === 'fusion') {
|
} else if (_compositeMode === 'fusion') {
|
||||||
btn.classList.add('panel-mode-fusion');
|
btn.classList.add('panel-mode-fusion');
|
||||||
if (icon) icon.className = 'fa fa-users';
|
if (icon) icon.className = 'fa fa-users';
|
||||||
@@ -278,6 +284,7 @@ function initCompositeMode(panelMode, hasAgents) {
|
|||||||
_compositeHasAgents = !!hasAgents;
|
_compositeHasAgents = !!hasAgents;
|
||||||
_compositeMode = (panelMode === 'fusion' || panelMode === 'moa') ? panelMode : 'off';
|
_compositeMode = (panelMode === 'fusion' || panelMode === 'moa') ? panelMode : 'off';
|
||||||
_planModeActive = false;
|
_planModeActive = false;
|
||||||
|
_ultracodeActive = false;
|
||||||
_renderCompositeBtn();
|
_renderCompositeBtn();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,12 +294,13 @@ async function cycleCompositeMode() {
|
|||||||
var idx = _COMPOSITE_ORDER.indexOf(_compositeMode);
|
var idx = _COMPOSITE_ORDER.indexOf(_compositeMode);
|
||||||
_compositeMode = _COMPOSITE_ORDER[(idx + 1) % _COMPOSITE_ORDER.length];
|
_compositeMode = _COMPOSITE_ORDER[(idx + 1) % _COMPOSITE_ORDER.length];
|
||||||
_planModeActive = (_compositeMode === 'plan');
|
_planModeActive = (_compositeMode === 'plan');
|
||||||
|
_ultracodeActive = (_compositeMode === 'ultracode');
|
||||||
_renderCompositeBtn();
|
_renderCompositeBtn();
|
||||||
|
|
||||||
// Nur Fusion/MoA/Off brauchen einen Server-Call (Plan ist transient). Auf die serialisierte
|
// Nur Fusion/MoA/Off brauchen einen Server-Call (Plan + Ultracode sind transient pro Request).
|
||||||
// Kette legen, damit aufeinanderfolgende Klicks in Reihenfolge committen (letzter Klick gewinnt)
|
// Auf die serialisierte Kette legen, damit aufeinanderfolgende Klicks in Reihenfolge committen
|
||||||
// und sendMessage das offene PATCH abwarten kann.
|
// (letzter Klick gewinnt) und sendMessage das offene PATCH abwarten kann.
|
||||||
if (_compositeMode !== 'plan') {
|
if (_compositeMode !== 'plan' && _compositeMode !== 'ultracode') {
|
||||||
var sid = getCurrentSessionId();
|
var sid = getCurrentSessionId();
|
||||||
if (sid) {
|
if (sid) {
|
||||||
var targetMode = _compositeMode;
|
var targetMode = _compositeMode;
|
||||||
@@ -310,6 +318,8 @@ async function cycleCompositeMode() {
|
|||||||
showToast('Modus: Aus', 'info');
|
showToast('Modus: Aus', 'info');
|
||||||
} else if (_compositeMode === 'plan') {
|
} else if (_compositeMode === 'plan') {
|
||||||
showToast('Plan-Modus aktiv', 'info');
|
showToast('Plan-Modus aktiv', 'info');
|
||||||
|
} else if (_compositeMode === 'ultracode') {
|
||||||
|
showToast('Ultracode aktiv – diese Anfrage läuft als dynamischer Workflow (mehr Tokens/Zeit)', 'info');
|
||||||
} else if (!_compositeHasAgents) {
|
} else if (!_compositeHasAgents) {
|
||||||
showToast('Panel: ' + (_compositeMode === 'moa' ? 'MoA' : 'Fusion') + ' aktiv – aber keine Panel-Agenten konfiguriert (im Session-Bearbeiten festlegen)', 'info');
|
showToast('Panel: ' + (_compositeMode === 'moa' ? 'MoA' : 'Fusion') + ' aktiv – aber keine Panel-Agenten konfiguriert (im Session-Bearbeiten festlegen)', 'info');
|
||||||
} else {
|
} else {
|
||||||
@@ -560,6 +570,10 @@ async function sendMessage() {
|
|||||||
msgText += `\n<hint>Plan-Modus ist aktiv. KEINE Dateien ändern, KEINE Shell-Befehle (außer read-only), KEINE Commits. Nur analysieren, Rückfragen stellen und Plan erstellen.</hint>`;
|
msgText += `\n<hint>Plan-Modus ist aktiv. KEINE Dateien ändern, KEINE Shell-Befehle (außer read-only), KEINE Commits. Nur analysieren, Rückfragen stellen und Plan erstellen.</hint>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_ultracodeActive) {
|
||||||
|
msgText += `\n<hint>Ultracode ist aktiv: Bearbeite diese Anfrage als dynamischen Workflow – use a workflow. Fächere die Arbeit über mehrere Subagenten parallel auf, lass Befunde adversarial gegenprüfen und synthetisiere am Ende. Nutze dafür das Workflow-Tool.</hint>`;
|
||||||
|
}
|
||||||
|
|
||||||
let inlineImages = []; // Bilder für Inline-Anzeige
|
let inlineImages = []; // Bilder für Inline-Anzeige
|
||||||
if (pendingFiles.length > 0) {
|
if (pendingFiles.length > 0) {
|
||||||
content = [];
|
content = [];
|
||||||
@@ -595,7 +609,7 @@ async function sendMessage() {
|
|||||||
// (z.B. MoA → nur 1 Runde, weil das Backend noch 'fusion' liest).
|
// (z.B. MoA → nur 1 Runde, weil das Backend noch 'fusion' liest).
|
||||||
await _modePersist;
|
await _modePersist;
|
||||||
|
|
||||||
await _runStream((signal) => apiChat(content, getCurrentSessionId(), signal, _planModeActive));
|
await _runStream((signal) => apiChat(content, getCurrentSessionId(), signal, _planModeActive, _ultracodeActive));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Steering (Einwurf in die laufende Chain) ──
|
// ── Steering (Einwurf in die laufende Chain) ──
|
||||||
|
|||||||
Reference in new issue
Block a user