@dreb/coding-agent 2.60.1 → 2.61.1
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.
- package/README.md +6 -4
- package/dist/cli/args.d.ts +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +2 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +49 -6
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +122 -25
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/dispatch-arbiter.d.ts.map +1 -1
- package/dist/core/dispatch-arbiter.js +2 -2
- package/dist/core/dispatch-arbiter.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +1 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/settings-manager.d.ts +4 -4
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/thinking.d.ts.map +1 -1
- package/dist/core/thinking.js +9 -1
- package/dist/core/thinking.js.map +1 -1
- package/dist/core/tools/index.d.ts +6 -6
- package/dist/core/tools/subagent.d.ts +9 -9
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +4 -2
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +6 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +3 -2
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +2 -1
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.d.ts +7 -2
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js +19 -12
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +27 -12
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +1 -6
- package/dist/modes/interactive/theme/light.json +1 -6
- package/dist/modes/interactive/theme/theme-schema.json +10 -2
- package/dist/modes/interactive/theme/theme.d.ts +2 -2
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +9 -3
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +1 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +2 -1
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +2 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +3 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/agent-models.md +1 -1
- package/docs/custom-provider.md +2 -2
- package/docs/extensions.md +1 -1
- package/docs/models.md +3 -1
- package/docs/rpc.md +21 -10
- package/docs/sdk.md +1 -1
- package/docs/settings.md +2 -2
- package/docs/themes.md +3 -1
- package/docs/tree.md +1 -1
- package/docs/tui.md +1 -1
- package/examples/extensions/dynamic-resources/dynamic.json +1 -0
- package/examples/extensions/preset.ts +1 -1
- package/package.json +1 -1
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { copyFileSync, existsSync, mkdirSync, realpathSync, writeFileSync } from "node:fs";
|
|
16
16
|
import { tmpdir } from "node:os";
|
|
17
17
|
import { basename, dirname, join, resolve } from "node:path";
|
|
18
|
-
import { isContextOverflow, modelsAreEqual, resetApiProviders, supportsXhigh } from "@dreb/ai";
|
|
18
|
+
import { isContextOverflow, modelsAreEqual, resetApiProviders, supportsMax, supportsXhigh } from "@dreb/ai";
|
|
19
19
|
import { getDocsPath } from "../config.js";
|
|
20
20
|
import { theme } from "../modes/interactive/theme/theme.js";
|
|
21
21
|
import { sleep } from "../utils/sleep.js";
|
|
@@ -69,8 +69,10 @@ export function parseSkillBlock(text) {
|
|
|
69
69
|
// ============================================================================
|
|
70
70
|
/** Standard thinking levels */
|
|
71
71
|
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
|
|
72
|
-
/** Thinking levels including xhigh (for supported models) */
|
|
72
|
+
/** Thinking levels including xhigh (for supported models). */
|
|
73
73
|
const THINKING_LEVELS_WITH_XHIGH = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
74
|
+
/** Complete ordered scale, including the native max tier for supported models. */
|
|
75
|
+
const THINKING_LEVELS_WITH_MAX = [...THINKING_LEVELS_WITH_XHIGH, "max"];
|
|
74
76
|
const MIN_PERFORMANCE_DURATION_MS = 10;
|
|
75
77
|
// ============================================================================
|
|
76
78
|
// AgentSession Class
|
|
@@ -1895,6 +1897,8 @@ export class AgentSession {
|
|
|
1895
1897
|
getAvailableThinkingLevels() {
|
|
1896
1898
|
if (!this.supportsThinking())
|
|
1897
1899
|
return ["off"];
|
|
1900
|
+
if (this.supportsMaxThinking())
|
|
1901
|
+
return THINKING_LEVELS_WITH_MAX;
|
|
1898
1902
|
return this.supportsXhighThinking() ? THINKING_LEVELS_WITH_XHIGH : THINKING_LEVELS;
|
|
1899
1903
|
}
|
|
1900
1904
|
/**
|
|
@@ -1903,6 +1907,10 @@ export class AgentSession {
|
|
|
1903
1907
|
supportsXhighThinking() {
|
|
1904
1908
|
return this.model ? supportsXhigh(this.model) : false;
|
|
1905
1909
|
}
|
|
1910
|
+
/** Check if the current model supports the native max thinking level. */
|
|
1911
|
+
supportsMaxThinking() {
|
|
1912
|
+
return this.model ? supportsMax(this.model) : false;
|
|
1913
|
+
}
|
|
1906
1914
|
/**
|
|
1907
1915
|
* Check if current model supports thinking/reasoning.
|
|
1908
1916
|
*/
|
|
@@ -1919,7 +1927,7 @@ export class AgentSession {
|
|
|
1919
1927
|
return this.thinkingLevel;
|
|
1920
1928
|
}
|
|
1921
1929
|
_clampThinkingLevel(level, availableLevels) {
|
|
1922
|
-
const ordered =
|
|
1930
|
+
const ordered = THINKING_LEVELS_WITH_MAX;
|
|
1923
1931
|
const available = new Set(availableLevels);
|
|
1924
1932
|
const requestedIndex = ordered.indexOf(level);
|
|
1925
1933
|
if (requestedIndex === -1) {
|
|
@@ -3062,21 +3070,71 @@ export class AgentSession {
|
|
|
3062
3070
|
this._emit({ type: "session_name_changed", name: this.sessionName ?? "" });
|
|
3063
3071
|
}
|
|
3064
3072
|
/**
|
|
3065
|
-
* Create a fork from a specific entry.
|
|
3073
|
+
* Create a fork from a specific entry. The fork point may be any user or
|
|
3074
|
+
* assistant message in the transcript; branch semantics depend on the role:
|
|
3075
|
+
*
|
|
3076
|
+
* - **Assistant message** -> the new branch *includes* the selected response
|
|
3077
|
+
* (and everything before it); no editor pre-fill. "Continue from this answer."
|
|
3078
|
+
* Forking at the last assistant message keeps the entire current state.
|
|
3079
|
+
* - **User message** -> rewind to *before* the selected message (branch from its
|
|
3080
|
+
* parent, dropping the message and everything after it) and offer its text as
|
|
3081
|
+
* editor pre-fill. "Edit / re-ask this question."
|
|
3082
|
+
*
|
|
3066
3083
|
* Emits before_fork/fork session events to extensions.
|
|
3067
3084
|
*
|
|
3068
|
-
* @param entryId ID of the entry to fork from
|
|
3085
|
+
* @param entryId ID of the message entry to fork from
|
|
3069
3086
|
* @returns Object with:
|
|
3070
|
-
* - selectedText: The
|
|
3087
|
+
* - selectedText: The selected user message text for editor pre-fill (empty
|
|
3088
|
+
* when forking at an assistant message).
|
|
3071
3089
|
* - cancelled: True if an extension cancelled the fork
|
|
3072
3090
|
*/
|
|
3073
3091
|
async fork(entryId) {
|
|
3074
|
-
const previousSessionFile = this.sessionFile;
|
|
3075
3092
|
const selectedEntry = this.sessionManager.getEntry(entryId);
|
|
3076
|
-
if (!selectedEntry ||
|
|
3093
|
+
if (!selectedEntry ||
|
|
3094
|
+
selectedEntry.type !== "message" ||
|
|
3095
|
+
(selectedEntry.message.role !== "user" && selectedEntry.message.role !== "assistant")) {
|
|
3077
3096
|
throw new Error("Invalid entry ID for forking");
|
|
3078
3097
|
}
|
|
3079
|
-
|
|
3098
|
+
if (selectedEntry.message.role === "assistant") {
|
|
3099
|
+
// Continue-from-answer: branch from the assistant entry itself so it (and
|
|
3100
|
+
// everything before it) is retained. No editor pre-fill.
|
|
3101
|
+
//
|
|
3102
|
+
// Reject turns that can't be safely branched from (interrupted, or waiting
|
|
3103
|
+
// on tool results) — branching there would silently produce a branch that
|
|
3104
|
+
// doesn't match the selected turn. See _isForkableAssistant.
|
|
3105
|
+
if (!this._isForkableAssistant(selectedEntry.message)) {
|
|
3106
|
+
throw new Error("Cannot fork at this assistant turn: it was interrupted or is still waiting on tool results");
|
|
3107
|
+
}
|
|
3108
|
+
const { cancelled } = await this._performFork(entryId, () => {
|
|
3109
|
+
this.sessionManager.createBranchedSession(entryId);
|
|
3110
|
+
});
|
|
3111
|
+
return { selectedText: "", cancelled };
|
|
3112
|
+
}
|
|
3113
|
+
const selectedText = this._extractMessageText(selectedEntry.message.content);
|
|
3114
|
+
// Rewind to *before* the selected user message by branching from its parent,
|
|
3115
|
+
// so the selected message (and everything after it) is dropped and its text is
|
|
3116
|
+
// offered as editor pre-fill.
|
|
3117
|
+
const { cancelled } = await this._performFork(entryId, (previousSessionFile) => {
|
|
3118
|
+
if (!selectedEntry.parentId) {
|
|
3119
|
+
this.sessionManager.newSession({ parentSession: previousSessionFile });
|
|
3120
|
+
}
|
|
3121
|
+
else {
|
|
3122
|
+
this.sessionManager.createBranchedSession(selectedEntry.parentId);
|
|
3123
|
+
}
|
|
3124
|
+
});
|
|
3125
|
+
return { selectedText, cancelled };
|
|
3126
|
+
}
|
|
3127
|
+
/**
|
|
3128
|
+
* Shared fork machinery: emit the cancellable session_before_fork event,
|
|
3129
|
+
* clear pending state, create the branch via the supplied strategy, reload
|
|
3130
|
+
* the conversation, and emit session_fork.
|
|
3131
|
+
*
|
|
3132
|
+
* @param entryId Entry the fork is anchored to (reported to extensions).
|
|
3133
|
+
* @param branch Strategy that creates the branched/new session. Receives the
|
|
3134
|
+
* previous session file so callers can set it as the parent when needed.
|
|
3135
|
+
*/
|
|
3136
|
+
async _performFork(entryId, branch) {
|
|
3137
|
+
const previousSessionFile = this.sessionFile;
|
|
3080
3138
|
let skipConversationRestore = false;
|
|
3081
3139
|
// Emit session_before_fork event (can be cancelled)
|
|
3082
3140
|
if (this._extensionRunner?.hasHandlers("session_before_fork")) {
|
|
@@ -3085,18 +3143,13 @@ export class AgentSession {
|
|
|
3085
3143
|
entryId,
|
|
3086
3144
|
}));
|
|
3087
3145
|
if (result?.cancel) {
|
|
3088
|
-
return {
|
|
3146
|
+
return { cancelled: true };
|
|
3089
3147
|
}
|
|
3090
3148
|
skipConversationRestore = result?.skipConversationRestore ?? false;
|
|
3091
3149
|
}
|
|
3092
3150
|
// Clear pending messages (bound to old session state)
|
|
3093
3151
|
this._pendingNextTurnMessages = [];
|
|
3094
|
-
|
|
3095
|
-
this.sessionManager.newSession({ parentSession: previousSessionFile });
|
|
3096
|
-
}
|
|
3097
|
-
else {
|
|
3098
|
-
this.sessionManager.createBranchedSession(selectedEntry.parentId);
|
|
3099
|
-
}
|
|
3152
|
+
branch(previousSessionFile);
|
|
3100
3153
|
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
3101
3154
|
// Reload messages from entries (works for both file and in-memory mode)
|
|
3102
3155
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
@@ -3111,7 +3164,7 @@ export class AgentSession {
|
|
|
3111
3164
|
if (!skipConversationRestore) {
|
|
3112
3165
|
this.agent.replaceMessages(sessionContext.messages);
|
|
3113
3166
|
}
|
|
3114
|
-
return {
|
|
3167
|
+
return { cancelled: false };
|
|
3115
3168
|
}
|
|
3116
3169
|
// =========================================================================
|
|
3117
3170
|
// Tree Navigation
|
|
@@ -3245,7 +3298,7 @@ export class AgentSession {
|
|
|
3245
3298
|
if (targetEntry.type === "message" && targetEntry.message.role === "user") {
|
|
3246
3299
|
// User message: leaf = parent (null if root), text goes to editor
|
|
3247
3300
|
newLeafId = targetEntry.parentId;
|
|
3248
|
-
editorText = this.
|
|
3301
|
+
editorText = this._extractMessageText(targetEntry.message.content);
|
|
3249
3302
|
}
|
|
3250
3303
|
else if (targetEntry.type === "custom_message") {
|
|
3251
3304
|
// Custom message: leaf = parent (null if root), text goes to editor
|
|
@@ -3307,24 +3360,68 @@ export class AgentSession {
|
|
|
3307
3360
|
}
|
|
3308
3361
|
}
|
|
3309
3362
|
/**
|
|
3310
|
-
* Get all
|
|
3363
|
+
* Get all forkable messages (user *and* assistant) for the fork selector.
|
|
3364
|
+
*
|
|
3365
|
+
* Each entry carries its role so callers can label it and choose the right
|
|
3366
|
+
* fork semantics (assistant = continue-from-answer, user = rewind + re-ask).
|
|
3367
|
+
* A forkable assistant turn with no renderable text (e.g. a thinking-only
|
|
3368
|
+
* turn) still appears as a fork point, with a generic label.
|
|
3369
|
+
*
|
|
3370
|
+
* Assistant turns that cannot be safely branched from (interrupted turns, or
|
|
3371
|
+
* turns containing a tool call whose result lives in a descendant entry) are
|
|
3372
|
+
* excluded — see _isForkableAssistant.
|
|
3311
3373
|
*/
|
|
3312
|
-
|
|
3374
|
+
getForkableMessages() {
|
|
3313
3375
|
const entries = this.sessionManager.getEntries();
|
|
3314
3376
|
const result = [];
|
|
3315
3377
|
for (const entry of entries) {
|
|
3316
3378
|
if (entry.type !== "message")
|
|
3317
3379
|
continue;
|
|
3318
|
-
|
|
3380
|
+
const role = entry.message.role;
|
|
3381
|
+
if (role !== "user" && role !== "assistant")
|
|
3319
3382
|
continue;
|
|
3320
|
-
const text = this.
|
|
3321
|
-
if (
|
|
3322
|
-
|
|
3383
|
+
const text = this._extractMessageText(entry.message.content);
|
|
3384
|
+
if (role === "user") {
|
|
3385
|
+
// Preserve existing behavior: skip empty user messages.
|
|
3386
|
+
if (text)
|
|
3387
|
+
result.push({ entryId: entry.id, text, role });
|
|
3388
|
+
}
|
|
3389
|
+
else {
|
|
3390
|
+
// Only offer assistant turns that can be safely branched from.
|
|
3391
|
+
if (!this._isForkableAssistant(entry.message))
|
|
3392
|
+
continue;
|
|
3393
|
+
result.push({ entryId: entry.id, text: text || "(assistant response)", role });
|
|
3323
3394
|
}
|
|
3324
3395
|
}
|
|
3325
3396
|
return result;
|
|
3326
3397
|
}
|
|
3327
|
-
|
|
3398
|
+
/**
|
|
3399
|
+
* Whether an assistant turn can be safely used as a fork point.
|
|
3400
|
+
*
|
|
3401
|
+
* Forking anchors on the entry's ancestors only (SessionManager.getBranch
|
|
3402
|
+
* walks parentId upward), and errored/aborted turns are dropped by
|
|
3403
|
+
* transformMessages() before every request. Two kinds of assistant turn
|
|
3404
|
+
* therefore produce a branch that silently does NOT match what was selected:
|
|
3405
|
+
*
|
|
3406
|
+
* - stopReason "error"/"aborted": transformMessages() skips the turn, so the
|
|
3407
|
+
* reply vanishes from context on the next request (defeating "continue from
|
|
3408
|
+
* this answer", and risking back-to-back user messages on strict providers).
|
|
3409
|
+
* - turns containing tool calls: their tool results are *descendant* entries a
|
|
3410
|
+
* branch cannot include, so transformMessages() substitutes a fabricated
|
|
3411
|
+
* "No result provided" (isError) result — telling the model a successful
|
|
3412
|
+
* tool call failed.
|
|
3413
|
+
*
|
|
3414
|
+
* A completed answer (the intended "continue from here" target) has a terminal
|
|
3415
|
+
* stopReason and no unresolved tool calls, so it passes.
|
|
3416
|
+
*/
|
|
3417
|
+
_isForkableAssistant(message) {
|
|
3418
|
+
if (message.stopReason === "error" || message.stopReason === "aborted")
|
|
3419
|
+
return false;
|
|
3420
|
+
if (Array.isArray(message.content) && message.content.some((c) => c.type === "toolCall"))
|
|
3421
|
+
return false;
|
|
3422
|
+
return true;
|
|
3423
|
+
}
|
|
3424
|
+
_extractMessageText(content) {
|
|
3328
3425
|
if (typeof content === "string")
|
|
3329
3426
|
return content;
|
|
3330
3427
|
if (Array.isArray(content)) {
|