@yeaft/webchat-agent 1.0.423 → 1.0.425

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
@@ -16,6 +16,6 @@
16
16
  </head>
17
17
  <body>
18
18
  <div id="app"></div>
19
- <script type="module" src="app.bundle.js?v=f877da8f"></script>
19
+ <script type="module" src="app.bundle.js?v=50a62ea4"></script>
20
20
  </body>
21
21
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.423",
3
+ "version": "1.0.425",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -120,6 +120,7 @@ export async function handleWriteFile(msg) {
120
120
 
121
121
  export async function handleListDirectory(msg) {
122
122
  const { conversationId, requestId, dirPath, _requestUserId, _requestClientId } = msg;
123
+ const directoryPickerScope = msg.directoryPickerScope === 'agent' ? 'agent' : undefined;
123
124
  const conv = ctx.conversations.get(conversationId);
124
125
  const workDir = msg.workDir || conv?.workDir || ctx.CONFIG.workDir;
125
126
 
@@ -137,6 +138,7 @@ export async function handleListDirectory(msg) {
137
138
  sendWorkbenchResult(ctx, msg, {
138
139
  type: 'directory_listing',
139
140
  conversationId,
141
+ ...(directoryPickerScope ? { directoryPickerScope } : {}),
140
142
  requestId,
141
143
  _requestUserId,
142
144
  _requestClientId,
@@ -154,6 +156,7 @@ export async function handleListDirectory(msg) {
154
156
  sendWorkbenchResult(ctx, msg, {
155
157
  type: 'directory_listing',
156
158
  conversationId,
159
+ ...(directoryPickerScope ? { directoryPickerScope } : {}),
157
160
  requestId,
158
161
  _requestUserId,
159
162
  _requestClientId,
@@ -165,6 +168,7 @@ export async function handleListDirectory(msg) {
165
168
  sendWorkbenchResult(ctx, msg, {
166
169
  type: 'directory_listing',
167
170
  conversationId,
171
+ ...(directoryPickerScope ? { directoryPickerScope } : {}),
168
172
  requestId,
169
173
  _requestUserId,
170
174
  _requestClientId,
@@ -213,6 +217,7 @@ export async function handleListDirectory(msg) {
213
217
  sendWorkbenchResult(ctx, msg, {
214
218
  type: 'directory_listing',
215
219
  conversationId,
220
+ ...(directoryPickerScope ? { directoryPickerScope } : {}),
216
221
  requestId,
217
222
  _requestUserId,
218
223
  _requestClientId,
@@ -223,6 +228,7 @@ export async function handleListDirectory(msg) {
223
228
  sendWorkbenchResult(ctx, msg, {
224
229
  type: 'directory_listing',
225
230
  conversationId,
231
+ ...(directoryPickerScope ? { directoryPickerScope } : {}),
226
232
  requestId,
227
233
  _requestUserId,
228
234
  _requestClientId,
package/yeaft/effort.js CHANGED
@@ -61,7 +61,7 @@ export const SCENARIO_EFFORT = Object.freeze({
61
61
  * Pick the effort level for a given query context.
62
62
  *
63
63
  * Decision order:
64
- * 1. If userEffort is a valid Effort ('minimal'|'low'|'medium'|'high'|'xhigh'|'max'),
64
+ * 1. If userEffort is a valid Effort ('minimal'|'low'|'medium'|'high'|'xhigh'|'max'|'ultra'),
65
65
  * return it unchanged. This is the explicit override path —
66
66
  * `/max` prefix, Settings slider, or API caller.
67
67
  * 2. If toolLoopTurns >= LONG_LOOP_TURN_THRESHOLD, upgrade the
@@ -74,7 +74,7 @@ export const SCENARIO_EFFORT = Object.freeze({
74
74
  * already consumed in the current `query()` call.
75
75
  * @param {unknown} [ctx.userEffort=null] — User-supplied override.
76
76
  * Invalid values are ignored (fall through to scenario path).
77
- * @returns {'minimal'|'low'|'medium'|'high'|'xhigh'|'max'} Resolved effort. Never null —
77
+ * @returns {'minimal'|'low'|'medium'|'high'|'xhigh'|'max'|'ultra'} Resolved effort. Never null —
78
78
  * the adapter/router is responsible for dropping it when the
79
79
  * feature flag is off or the model doesn't support thinking.
80
80
  */
@@ -97,7 +97,7 @@ export function pickEffort({ scenario = 'chat', toolLoopTurns = 0, userEffort =
97
97
  }
98
98
 
99
99
  /**
100
- * Parse a user prompt for `/max`, `/xhigh`, `/high`, `/medium`, `/low` prefix
100
+ * Parse a user prompt for `/ultra`, `/max`, `/xhigh`, `/high`, `/medium`, `/low` prefix
101
101
  * commands. Returns `{ effort, cleanedPrompt }` where cleanedPrompt has
102
102
  * the prefix (plus one trailing space) stripped.
103
103
  *
@@ -108,11 +108,11 @@ export function pickEffort({ scenario = 'chat', toolLoopTurns = 0, userEffort =
108
108
  * via `!` or `/skill:` instead to avoid collision.
109
109
  *
110
110
  * @param {string} prompt
111
- * @returns {{ effort: 'low'|'medium'|'high'|'xhigh'|'max'|null, cleanedPrompt: string }}
111
+ * @returns {{ effort: 'low'|'medium'|'high'|'xhigh'|'max'|'ultra'|null, cleanedPrompt: string }}
112
112
  */
113
113
  export function parseEffortPrefix(prompt) {
114
114
  if (typeof prompt !== 'string') return { effort: null, cleanedPrompt: prompt };
115
- const m = prompt.match(/^\/(max|xhigh|high|medium|low)(\s+|$)/);
115
+ const m = prompt.match(/^\/(ultra|max|xhigh|high|medium|low)(\s+|$)/);
116
116
  if (!m) return { effort: null, cleanedPrompt: prompt };
117
117
  const effort = m[1];
118
118
  const cleanedPrompt = prompt.slice(m[0].length);
package/yeaft/engine.js CHANGED
@@ -2046,9 +2046,9 @@ export class Engine {
2046
2046
  * @param {string} params.prompt - The user prompt (required, non-empty).
2047
2047
  * @param {Array} [params.messages] - Prior conversation messages.
2048
2048
  * @param {AbortSignal} [params.signal] - Abort signal.
2049
- * @param {'low'|'medium'|'high'|'max'|null} [params.userEffort] -
2049
+ * @param {'low'|'medium'|'high'|'xhigh'|'max'|'ultra'|null} [params.userEffort] -
2050
2050
  * task-327b: explicit per-query effort override (from Settings or
2051
- * API caller). `/max`/`/high`/`/medium`/`/low` prefixes in prompt
2051
+ * API caller). `/ultra`/`/max`/`/high`/`/medium`/`/low` prefixes in prompt
2052
2052
  * also set this. Null/invalid → scenario decision tree decides.
2053
2053
  * @param {string} [params.scenario='chat'] - task-327b: scenario tag
2054
2054
  * forwarded to the effort decision tree. See effort.js
@@ -3031,10 +3031,11 @@ export class Engine {
3031
3031
  allowRouterEscalate: thinkingCfg.allowRouterEscalate !== false,
3032
3032
  });
3033
3033
  // Only adopt the chain's choice when it strengthens the
3034
- // baseline. We never weaken below pickEffort (e.g. consolidate
3035
- // = 'max' must not be downgraded to 'high' just because the VP
3036
- // default is 'high').
3037
- if (resolved.value === 'max' || (resolved.value === 'high' && resolvedEffort === 'low')) {
3034
+ // baseline. We never weaken below pickEffort (e.g. explicit
3035
+ // 'ultra' or consolidate='max' must not be downgraded by a VP
3036
+ // default or router plan).
3037
+ if (resolvedEffort !== 'ultra'
3038
+ && (resolved.value === 'max' || (resolved.value === 'high' && resolvedEffort === 'low'))) {
3038
3039
  resolvedEffort = resolved.value;
3039
3040
  }
3040
3041
  }
@@ -254,7 +254,7 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
254
254
  // ─── Streaming ──────────────────────────────────────────
255
255
 
256
256
  /**
257
- * @param {{ model: string, system: string, messages: import('./adapter.js').UnifiedMessage[], tools?: import('./adapter.js').UnifiedToolDef[], maxTokens?: number, effort?: 'minimal'|'low'|'medium'|'high'|'xhigh'|'max', effortSource?: 'user'|'auto', effortContext?: object, extraBody?: object, signal?: AbortSignal, onRawExchange?: ({rawRequest, rawResponse}) => void, onRequestStart?: () => void }} params
257
+ * @param {{ model: string, system: string, messages: import('./adapter.js').UnifiedMessage[], tools?: import('./adapter.js').UnifiedToolDef[], maxTokens?: number, effort?: 'minimal'|'low'|'medium'|'high'|'xhigh'|'max'|'ultra', effortSource?: 'user'|'auto', effortContext?: object, extraBody?: object, signal?: AbortSignal, onRawExchange?: ({rawRequest, rawResponse}) => void, onRequestStart?: () => void }} params
258
258
  *
259
259
  * NOTE on `extraBody`: any keys you spread here are merged verbatim into
260
260
  * the wire body and — because the verbatim debug feature is intentionally
package/yeaft/models.js CHANGED
@@ -34,9 +34,9 @@ import { lookupModelLimitSync } from './llm/models-dev.js';
34
34
  * @property {'anthropic' | 'anthropic-adaptive' | 'openai-reasoning' | 'none'} [thinkingProtocol] — task-327a:
35
35
  * 'anthropic' → thinking:{type:'enabled', budget_tokens:N}
36
36
  * 'anthropic-adaptive' → thinking:{type:'adaptive'} + output_config:{effort}
37
- * 'openai-reasoning' → reasoning:{effort:'minimal'|'low'|'medium'|'high'|'xhigh'|'max'}
37
+ * 'openai-reasoning' → reasoning:{effort:'minimal'|'low'|'medium'|'high'|'xhigh'|'max'|'ultra'}
38
38
  * 'none' (default) → parameter silently dropped by router
39
- * @property {'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null} [defaultEffort] — adapter-level default
39
+ * @property {'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'ultra' | null} [defaultEffort] — adapter-level default
40
40
  * when caller doesn't specify effort (null = no default / decision-tree decides).
41
41
  * @property {number} [maxBudgetTokens] — task-327a: for anthropic protocol, the cap used when
42
42
  * effort='max' (e.g. Opus 4 = 64K, Sonnet 4 = 32K). For openai-reasoning this field is unused
@@ -146,6 +146,7 @@ export const MODEL_REGISTRY = new Map([
146
146
  supportsThinking: true,
147
147
  thinkingProtocol: 'openai-reasoning',
148
148
  defaultEffort: null,
149
+ effortOptions: ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'],
149
150
  }],
150
151
  ['gpt-4.1', {
151
152
  provider: 'openai',
@@ -386,7 +387,7 @@ export function parseModelRef(ref) {
386
387
 
387
388
  /**
388
389
  * Valid effort levels accepted by Yeaft adapters.
389
- * @typedef {'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'} Effort
390
+ * @typedef {'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'ultra'} Effort
390
391
  */
391
392
 
392
393
  /**
@@ -410,7 +411,7 @@ export const ANTHROPIC_THINKING_BUDGETS = {
410
411
  * translation. Unsupported values are dropped; the adapter MUST NOT error.
411
412
  *
412
413
  * @param {Effort} effort
413
- * @returns {'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null}
414
+ * @returns {'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'ultra' | null}
414
415
  */
415
416
  export function mapEffortToOpenAIReasoning(effort) {
416
417
  if (!effort) return null;
@@ -421,12 +422,14 @@ export function mapEffortToOpenAIReasoning(effort) {
421
422
  case 'high': return 'high';
422
423
  case 'xhigh': return 'xhigh';
423
424
  case 'max': return 'max';
425
+ case 'ultra': return 'ultra';
424
426
  default: return null;
425
427
  }
426
428
  }
427
429
 
428
430
  export const OPENAI_REASONING_EFFORT_OPTIONS = ['minimal', 'low', 'medium', 'high', 'xhigh'];
429
431
  export const OPENAI_MAX_REASONING_EFFORT_OPTIONS = ['low', 'medium', 'high', 'xhigh', 'max'];
432
+ export const OPENAI_ULTRA_REASONING_EFFORT_OPTIONS = ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'];
430
433
  export const ANTHROPIC_MANUAL_EFFORT_OPTIONS = ['low', 'medium', 'high'];
431
434
  export const ANTHROPIC_ADAPTIVE_EFFORT_OPTIONS = ['low', 'medium', 'high', 'xhigh', 'max'];
432
435
  export const ANTHROPIC_ADAPTIVE_MAX_EFFORT_OPTIONS = ['low', 'medium', 'high', 'max'];
@@ -442,7 +445,25 @@ export const ANTHROPIC_ADAPTIVE_MAX_EFFORT_OPTIONS = ['low', 'medium', 'high', '
442
445
  // compatibility table explicitly supports `output_config` effort).
443
446
  export const DEEPSEEK_REASONING_EFFORT_OPTIONS = ['low', 'medium', 'high', 'xhigh', 'max'];
444
447
 
445
- const VALID_EFFORT_OPTIONS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
448
+ const VALID_EFFORT_OPTIONS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra']);
449
+
450
+ /**
451
+ * GPT models expose the provider-specific `ultra` tier starting at 5.5.
452
+ * Numeric comparison matters here: 5.10 is newer than 5.5.
453
+ */
454
+ export function modelSupportsUltraEffort(model) {
455
+ const id = parseModelRef(model).modelId.toLowerCase();
456
+ const match = id.match(/^gpt-(\d+)(?:\.(\d+))?(?=$|[-.]|\[)/);
457
+ if (!match) return false;
458
+ const major = Number(match[1]);
459
+ const minor = Number(match[2] || 0);
460
+ return major > 5 || (major === 5 && minor >= 5);
461
+ }
462
+
463
+ function fenceUltraEffortOptions(model, options) {
464
+ if (!Array.isArray(options) || modelSupportsUltraEffort(model)) return options;
465
+ return options.filter(effort => effort !== 'ultra');
466
+ }
446
467
 
447
468
  export function normalizeEffortOptions(options) {
448
469
  if (!Array.isArray(options)) return null;
@@ -460,16 +481,14 @@ function inferThinkingCapability(model) {
460
481
  const id = parseModelRef(model).modelId.toLowerCase();
461
482
  if (!id) return null;
462
483
 
463
- // GPT-5.6 adds the model-specific `max` tier. Keep this ahead of the generic
464
- // GPT inference so older OpenAI models do not advertise a value they reject.
465
- // Provider suffixes such as gpt-5.6-sol are variants of the same model family.
466
- if (/^gpt-5\.6($|[-.])/.test(id)) {
484
+ // Provider suffixes are variants of the same GPT model family.
485
+ if (modelSupportsUltraEffort(model)) {
467
486
  return {
468
487
  supportsThinking: true,
469
488
  thinkingProtocol: 'openai-reasoning',
470
489
  defaultEffort: null,
471
490
  maxBudgetTokens: null,
472
- effortOptions: OPENAI_MAX_REASONING_EFFORT_OPTIONS,
491
+ effortOptions: OPENAI_ULTRA_REASONING_EFFORT_OPTIONS,
473
492
  };
474
493
  }
475
494
 
@@ -557,23 +576,24 @@ export function thinkingBudgetForEffort(model, effort) {
557
576
  export function getThinkingCapability(model, context = {}) {
558
577
  const info = MODEL_REGISTRY.get(model);
559
578
  const modelId = parseModelRef(model).modelId;
560
- const overrideOptions = normalizeEffortOptions(context.effortOptions);
579
+ const overrideOptions = fenceUltraEffortOptions(model, normalizeEffortOptions(context.effortOptions));
561
580
  const overrideProtocol = context.thinkingProtocol || (
562
581
  context.protocol === 'anthropic'
563
582
  ? (/^deepseek/i.test(modelId) ? 'anthropic-adaptive' : 'anthropic')
564
583
  : context.protocol === 'openai-responses' ? 'openai-reasoning' : null
565
584
  );
585
+ const hasExplicitThinking = info && Object.prototype.hasOwnProperty.call(info, 'supportsThinking');
586
+ const familyInferred = inferThinkingCapability(model);
566
587
  if (context.supportsEffort === true || overrideOptions) {
567
588
  return {
568
589
  supportsThinking: true,
569
- thinkingProtocol: overrideProtocol || 'openai-reasoning',
570
- defaultEffort: context.defaultEffort ?? null,
571
- maxBudgetTokens: context.maxBudgetTokens ?? null,
572
- effortOptions: overrideOptions,
590
+ thinkingProtocol: overrideProtocol || info?.thinkingProtocol || familyInferred?.thinkingProtocol || 'openai-reasoning',
591
+ defaultEffort: context.defaultEffort ?? info?.defaultEffort ?? familyInferred?.defaultEffort ?? null,
592
+ maxBudgetTokens: context.maxBudgetTokens ?? info?.maxBudgetTokens ?? familyInferred?.maxBudgetTokens ?? null,
593
+ effortOptions: overrideOptions || info?.effortOptions || familyInferred?.effortOptions || null,
573
594
  };
574
595
  }
575
- const hasExplicitThinking = info && Object.prototype.hasOwnProperty.call(info, 'supportsThinking');
576
- let inferred = hasExplicitThinking ? null : inferThinkingCapability(model);
596
+ let inferred = hasExplicitThinking ? null : familyInferred;
577
597
  if (context.protocol === 'openai-responses' && !inferred && /^deepseek/i.test(parseModelRef(model).modelId)) {
578
598
  inferred = {
579
599
  supportsThinking: true,
@@ -608,7 +628,7 @@ export function getThinkingCapability(model, context = {}) {
608
628
  thinkingProtocol: info?.thinkingProtocol || inferred?.thinkingProtocol || 'none',
609
629
  defaultEffort: info?.defaultEffort ?? inferred?.defaultEffort ?? null,
610
630
  maxBudgetTokens: info?.maxBudgetTokens ?? inferred?.maxBudgetTokens ?? null,
611
- effortOptions: (info?.effortOptions || inferred?.effortOptions || null),
631
+ effortOptions: fenceUltraEffortOptions(model, info?.effortOptions || inferred?.effortOptions || null),
612
632
  };
613
633
  }
614
634
 
@@ -633,7 +653,7 @@ export function modelSupportsEffort(model, context = {}) {
633
653
  * @returns {Effort | null}
634
654
  */
635
655
  export function normalizeEffort(effort) {
636
- if (effort === 'minimal' || effort === 'low' || effort === 'medium' || effort === 'high' || effort === 'xhigh' || effort === 'max') {
656
+ if (effort === 'minimal' || effort === 'low' || effort === 'medium' || effort === 'high' || effort === 'xhigh' || effort === 'max' || effort === 'ultra') {
637
657
  return effort;
638
658
  }
639
659
  return null;
@@ -28,7 +28,7 @@ const CONFIG_FILE = 'config.json';
28
28
  const MODEL_SOURCE_EXPLICIT = 'explicit';
29
29
  const WRITABLE_KEYS = new Set(['model', 'modelEffort']);
30
30
  const STORED_KEYS = new Set([...WRITABLE_KEYS, 'modelSource']);
31
- const ALLOWED_EFFORTS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
31
+ const ALLOWED_EFFORTS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra']);
32
32
 
33
33
  export class SessionConfigError extends Error {
34
34
  constructor(code, message) {
@@ -99,7 +99,7 @@ export function validateSessionConfig(cfg) {
99
99
  }
100
100
  if ('modelEffort' in cfg && cfg.modelEffort !== null && cfg.modelEffort !== undefined && cfg.modelEffort !== '') {
101
101
  if (typeof cfg.modelEffort !== 'string' || !ALLOWED_EFFORTS.has(cfg.modelEffort.trim())) {
102
- throw new SessionConfigError('invalid_model_effort', 'modelEffort must be minimal, low, medium, high, xhigh, or max');
102
+ throw new SessionConfigError('invalid_model_effort', 'modelEffort must be minimal, low, medium, high, xhigh, max, or ultra');
103
103
  }
104
104
  }
105
105
  }
@@ -1163,7 +1163,7 @@ const ASK_USER_TIMEOUT_MS = 10 * 60_000;
1163
1163
 
1164
1164
  function isHighReasoningEffort(effort) {
1165
1165
  const value = typeof effort === 'string' ? effort.trim().toLowerCase() : '';
1166
- return value === 'high' || value === 'xhigh' || value === 'max';
1166
+ return value === 'high' || value === 'xhigh' || value === 'max' || value === 'ultra';
1167
1167
  }
1168
1168
 
1169
1169
  function queryTimeoutMsForSessionConfig(config = null) {
@@ -143,7 +143,7 @@ export function resolveWorkItemModel(config, vp, rawPolicy) {
143
143
  throw policyError(`Configured Work Center model is unavailable: ${model}`);
144
144
  }
145
145
  const effortOptions = Array.isArray(available?.effortOptions) ? available.effortOptions : [];
146
- const effortOrder = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
146
+ const effortOrder = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'];
147
147
  const requestedIndex = effortOrder.indexOf(policy.effort);
148
148
  const effort = !policy.effort || effortOptions.length === 0
149
149
  ? null
@@ -21,7 +21,7 @@ export const BUILT_IN_ACTION_TYPES = Object.freeze([
21
21
  const STAGE_TYPES = new Set(BUILT_IN_ACTION_TYPES);
22
22
  const ASSIGNMENT_MODES = new Set(['auto', 'pool', 'fixed', 'planned']);
23
23
  const MODEL_MODES = new Set(['inherit', 'primary', 'fast', 'specific']);
24
- const MODEL_EFFORTS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
24
+ const MODEL_EFFORTS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra']);
25
25
  const WORKSPACE_MODES = new Set(['shared', 'read', 'isolated-write', 'integrate']);
26
26
  const HIGH_EFFORT_ACTION_TYPES = new Set(['triage', 'research', 'design', 'diagnose', 'review']);
27
27
  const ACTION_CONTEXT_QUOTE_MAX_BYTES = 8 * 1024;