@toddzheng024/dscode-bundle 0.7.13 → 0.7.14
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/cordis.patch.yml +5 -1
- package/package.json +3 -2
- package/plugins/code-review/index.mjs +5 -2
- package/plugins/compaction/threshold.mjs +12 -0
- package/plugins/credentials/index.mjs +13 -0
- package/plugins/grok/adapter.mjs +114 -0
- package/plugins/grok/auth.mjs +76 -0
- package/plugins/grok/billing.mjs +146 -0
- package/plugins/grok/index.mjs +118 -0
- package/plugins/grok/models.mjs +129 -0
- package/plugins/grok/status.mjs +28 -0
- package/plugins/grok/wire.mjs +268 -0
- package/plugins/i18n/messages.mjs +6 -6
- package/plugins/providers/catalog.mjs +2 -0
- package/plugins/session-metrics/view.mjs +24 -1
- package/vendor/compaction-basic/index.js +91 -1
- package/vendor/persistent/index.js +10 -9
- package/vendor/terminal/index.js +5 -0
- package/vendor/tui/lib/app.mjs +40 -3
- package/vendor/tui/lib/dscode/chat.mjs +27 -7
- package/vendor/tui/lib/index.mjs +2 -0
- package/vendor/tui/lib/render/projection.mjs +4 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
// dscode-compaction-prefetch-v1
|
|
1
2
|
// dscode-compaction-threshold-v1
|
|
2
|
-
import { pricedCompactionPolicy as dscodePricedCompactionPolicy } from "../../plugins/compaction/threshold.mjs";
|
|
3
|
+
import { prefetchThresholdTokens as dscodePrefetchThresholdTokens, pricedCompactionPolicy as dscodePricedCompactionPolicy } from "../../plugins/compaction/threshold.mjs";
|
|
3
4
|
import z from "@deepseek-ai/schemastery";
|
|
4
5
|
import { CompactionEngine, CompactionId, ManualCompactionError, compactCheckpointSource, toolPairingBalancedAfter, toolPairingBalancedBefore } from "@deepseek-ai/dsh-compaction";
|
|
5
6
|
import { BlockAssembler, CONTEXT_WINDOW_EXCEEDED_CODE, LlmError, contentHasImage, createUserMessage, errorChain } from "@deepseek-ai/dsh-llm";
|
|
@@ -900,6 +901,7 @@ var BasicCompactionEngine = class extends CompactionEngine {
|
|
|
900
901
|
const targetKey = `${target.provider}/${target.model}`;
|
|
901
902
|
if (context === void 0) throw new TargetPressureConfigError(targetKey, `compaction-basic: no context capacity for ${targetKey}; configure contextWindow on that adapter model`);
|
|
902
903
|
const spec = resolveCompactSpec(await dscodePricedCompactionPolicy(this.config, policy), context.contextWindow);
|
|
904
|
+
this.dscodePlanPrefetch(agent, measurement, spec, context.contextWindow, signal);
|
|
903
905
|
if (measurement.totalTokens < spec.thresholdTokens) return null;
|
|
904
906
|
if (prune !== void 0) {
|
|
905
907
|
prune.pruneSession(agent.session);
|
|
@@ -907,6 +909,12 @@ var BasicCompactionEngine = class extends CompactionEngine {
|
|
|
907
909
|
}
|
|
908
910
|
if (measurement.totalTokens < spec.thresholdTokens) return null;
|
|
909
911
|
let result = null;
|
|
912
|
+
const dscodePrefetched = await this.dscodeCommitPrefetch(agent);
|
|
913
|
+
if (dscodePrefetched !== null) {
|
|
914
|
+
result = dscodePrefetched;
|
|
915
|
+
measurement = meter.measure(agent.session);
|
|
916
|
+
if (measurement.totalTokens < spec.thresholdTokens) return result;
|
|
917
|
+
}
|
|
910
918
|
for (let attempt = 0; attempt <= spec.compactionRetries; attempt += 1) {
|
|
911
919
|
const range = selectCompactableRange(agent.session, measurement, spec.retainTokens);
|
|
912
920
|
if (range === null) {
|
|
@@ -971,6 +979,88 @@ var BasicCompactionEngine = class extends CompactionEngine {
|
|
|
971
979
|
throw new ManualCompactionError("busy", "manual compaction requires an idle agent with no waking queued work", { cause: error });
|
|
972
980
|
}
|
|
973
981
|
}
|
|
982
|
+
/** dscode: the one background prefetch this engine may hold per session. */
|
|
983
|
+
dscodePrefetch = /* @__PURE__ */ new WeakMap();
|
|
984
|
+
/**
|
|
985
|
+
* dscode: start one background summarization of the oldest compactable span once the
|
|
986
|
+
* measured pressure crosses the prefetch mark, one lead below the priced threshold.
|
|
987
|
+
* Nothing is appended and no compaction lock is taken, so an invalidated or cancelled
|
|
988
|
+
* prefetch costs only the summarization call.
|
|
989
|
+
* @param agent - agent whose pressure the caller just priced.
|
|
990
|
+
* @param measurement - the measurement the caller priced.
|
|
991
|
+
* @param spec - the caller's resolved spec for this route.
|
|
992
|
+
* @param contextWindow - the route's context window.
|
|
993
|
+
* @param signal - live turn signal forwarded to the summarizer.
|
|
994
|
+
*/
|
|
995
|
+
dscodePlanPrefetch(agent, measurement, spec, contextWindow, signal) {
|
|
996
|
+
const session = agent.session;
|
|
997
|
+
const existing = this.dscodePrefetch.get(session);
|
|
998
|
+
if (existing !== void 0) {
|
|
999
|
+
if (existing.failure === null) return;
|
|
1000
|
+
this.dscodePrefetch.delete(session);
|
|
1001
|
+
}
|
|
1002
|
+
if (measurement.totalTokens >= spec.thresholdTokens) return;
|
|
1003
|
+
if (measurement.totalTokens < dscodePrefetchThresholdTokens(spec.thresholdTokens, contextWindow)) return;
|
|
1004
|
+
if (inspectCompactionEntryState(session).unmatchedCompactionStart !== void 0) return;
|
|
1005
|
+
const range = selectCompactableRange(session, measurement, spec.retainTokens);
|
|
1006
|
+
if (range === null) return;
|
|
1007
|
+
let selection;
|
|
1008
|
+
try {
|
|
1009
|
+
selection = validateSurfaceRegion(session, range.start, range.end);
|
|
1010
|
+
} catch (error) {
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
const prefetch = { compactionId: CompactionId(randomUUID()), summarized: null, failure: null, wait: null };
|
|
1014
|
+
this.dscodePrefetch.set(session, prefetch);
|
|
1015
|
+
prefetch.wait = (async () => {
|
|
1016
|
+
try {
|
|
1017
|
+
prefetch.summarized = await summarizeCompaction(this.regionDependencies(), prepareCompaction(this.regionDependencies(), session, selection), agent, prefetch.compactionId, void 0, signal);
|
|
1018
|
+
} catch (error) {
|
|
1019
|
+
prefetch.failure = error;
|
|
1020
|
+
}
|
|
1021
|
+
})();
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* dscode: commit the pending prefetch, or return null so the caller summarizes afresh.
|
|
1025
|
+
* A finished summary is validated before the durable marker opens; one still running
|
|
1026
|
+
* opens the marker first, so its wait carries the ordinary compaction indicator.
|
|
1027
|
+
* Commit replaces the prefetched span with its checkpoint and leaves everything
|
|
1028
|
+
* appended after it verbatim.
|
|
1029
|
+
* @param agent - agent whose pressure triggered the automatic compaction.
|
|
1030
|
+
* @returns the committed compaction result, or null when no prefetch was usable.
|
|
1031
|
+
*/
|
|
1032
|
+
async dscodeCommitPrefetch(agent) {
|
|
1033
|
+
const session = agent.session;
|
|
1034
|
+
const prefetch = this.dscodePrefetch.get(session);
|
|
1035
|
+
if (prefetch === void 0) return null;
|
|
1036
|
+
this.dscodePrefetch.delete(session);
|
|
1037
|
+
if (prefetch.failure !== null) return null;
|
|
1038
|
+
const dependencies = this.regionDependencies();
|
|
1039
|
+
if (prefetch.summarized !== null) try {
|
|
1040
|
+
assertSelectedSpanStable(dependencies, session, prefetch.summarized);
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
return null;
|
|
1043
|
+
}
|
|
1044
|
+
const openTurn = inspectCompactionEntryState(session).openTurn;
|
|
1045
|
+
const lifecycle = {
|
|
1046
|
+
compactionId: prefetch.compactionId,
|
|
1047
|
+
...openTurn === null ? {} : { turn: openTurn }
|
|
1048
|
+
};
|
|
1049
|
+
const startEvent = session.append("compaction/start", lifecycle);
|
|
1050
|
+
let closing = false;
|
|
1051
|
+
try {
|
|
1052
|
+
await prefetch.wait;
|
|
1053
|
+
if (prefetch.failure !== null) throw prefetch.failure;
|
|
1054
|
+
assertSelectedSpanStable(dependencies, session, prefetch.summarized);
|
|
1055
|
+
const pending = commitCompactionBody(session, startEvent, prefetch.summarized);
|
|
1056
|
+
closing = true;
|
|
1057
|
+
const endEvent = session.append("compaction/end", lifecycle);
|
|
1058
|
+
return completeCompaction(pending, endEvent);
|
|
1059
|
+
} catch (error) {
|
|
1060
|
+
if (!closing) session.append("compaction/end", { ...lifecycle, error: errorChain(error) });
|
|
1061
|
+
return null;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
974
1064
|
/** Bind the effective token meter and dynamically dispatched summarizer hook. */
|
|
975
1065
|
regionDependencies() {
|
|
976
1066
|
return {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// dscode-shell-capture-v1
|
|
1
2
|
// dscode-stdin-stall-v2
|
|
2
3
|
const STDIN_STALL_MS = 5000;
|
|
3
4
|
const STDIN_STALL_NOTE = "\n[Interrupted after 5s with no output: the command held the terminal without producing anything, and this tool cannot supply terminal input. Give it stdin (a heredoc, `< file` or `< /dev/null`) or use a non-interactive form.]";
|
|
@@ -174,7 +175,8 @@ function renderShellExitStatus(content, exitCode, signal) {
|
|
|
174
175
|
* @returns the complete model-facing result.
|
|
175
176
|
*/
|
|
176
177
|
async function respondToSessionExit(ctx, shells, owner, id, status, marker, fallback, fallbackTruncated, config) {
|
|
177
|
-
const
|
|
178
|
+
const window = ctx.terminals.read(owner, id, { capture: true });
|
|
179
|
+
const snapshot = window.text.length > 0 ? window : retainedScrollback(ctx, owner, id);
|
|
178
180
|
await shells.reset(owner, "persistent bash shell exited");
|
|
179
181
|
return [renderShellExitStatus(renderCaptured(partialOutput(snapshot, marker, fallback, fallbackTruncated), config.maxOutputChars), status.exitCode, status.signal), SHELL_RESET_MESSAGE].filter((part) => part.length > 0).join("\n");
|
|
180
182
|
}
|
|
@@ -272,6 +274,7 @@ async function executeCommand(ctx, shells, owner, command, config, upstream) {
|
|
|
272
274
|
try {
|
|
273
275
|
operation = ctx.terminals.startSend(owner, id, {
|
|
274
276
|
text: first ? wrapped : "",
|
|
277
|
+
capture: first,
|
|
275
278
|
submit: first,
|
|
276
279
|
signal: commandDeadline.signal
|
|
277
280
|
});
|
|
@@ -284,13 +287,11 @@ async function executeCommand(ctx, shells, owner, command, config, upstream) {
|
|
|
284
287
|
const incremental = operation.readOutput();
|
|
285
288
|
fallback = incremental.delta.length > 0 ? fallback + incremental.delta : result.viewport;
|
|
286
289
|
fallbackTruncated ||= incremental.truncated || result.truncated;
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
count: SCROLLBACK_PAGE_LINES
|
|
290
|
-
});
|
|
290
|
+
const captured = ctx.terminals.read(owner, id, { capture: true });
|
|
291
|
+
fallbackTruncated ||= captured.truncated;
|
|
291
292
|
const timedOut = timeoutOf(commandDeadline.signal, TIMEOUT_CODE);
|
|
292
293
|
if (timedOut !== void 0) {
|
|
293
|
-
const partial = renderCaptured(partialOutput(
|
|
294
|
+
const partial = renderCaptured(partialOutput(captured, marker, fallback, fallbackTruncated), config.maxOutputChars);
|
|
294
295
|
await shells.reset(owner, "persistent bash command timed out");
|
|
295
296
|
return [
|
|
296
297
|
`Your command timed out after ${Math.round(timedOut.timeoutMs / 1e3)} seconds or experienced an OOM error. Below is partial output:`,
|
|
@@ -302,12 +303,12 @@ async function executeCommand(ctx, shells, owner, command, config, upstream) {
|
|
|
302
303
|
await shells.reset(owner, "persistent bash command aborted");
|
|
303
304
|
commandDeadline.signal.throwIfAborted();
|
|
304
305
|
}
|
|
305
|
-
if (
|
|
306
|
-
const complete = commandOutput(
|
|
306
|
+
if (captured.text.includes(marker.end)) {
|
|
307
|
+
const complete = commandOutput(captured, marker);
|
|
307
308
|
if (complete !== void 0) return renderCaptured(complete, config.maxOutputChars) + (stdinStalled ? STDIN_STALL_NOTE : "");
|
|
308
309
|
}
|
|
309
310
|
if (result.sessionStatus.kind === "exited") return await respondToSessionExit(ctx, shells, owner, id, result.sessionStatus, marker, fallback, fallbackTruncated, config);
|
|
310
|
-
const partial = renderCaptured(partialOutput(
|
|
311
|
+
const partial = renderCaptured(partialOutput(captured, marker, fallback, fallbackTruncated), config.maxOutputChars);
|
|
311
312
|
if (result.waitReason === "stdin_read") stdinWaited = true;
|
|
312
313
|
if (result.waitReason === "stdin_read" && partial.length > 0) return partial;
|
|
313
314
|
if (stdinWaited && partial.length === 0 && !stdinStalled && Date.now() - startedAt >= STDIN_STALL_MS) {
|
package/vendor/terminal/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// dscode-shell-capture-v1
|
|
1
2
|
// dscode-no-history-expansion-v1
|
|
2
3
|
import { createRequire } from "node:module";
|
|
3
4
|
import { TerminalBackendCleanupError, TerminalError } from "@deepseek-ai/dsh-terminal";
|
|
@@ -254,6 +255,7 @@ function utf8Tail(text, maxBytes) {
|
|
|
254
255
|
truncated: true
|
|
255
256
|
};
|
|
256
257
|
}
|
|
258
|
+
const CAPTURE_MAX_BYTES = 4194304;
|
|
257
259
|
var BoundedTextBuffer = class {
|
|
258
260
|
maxBytes;
|
|
259
261
|
maxLines;
|
|
@@ -454,6 +456,7 @@ var LocalPtySession = class {
|
|
|
454
456
|
if (this.statusValue.kind === "exited") throw new Error("PTY session has exited");
|
|
455
457
|
if (this.active !== void 0) throw new TerminalError(`PTY session already has an active send${this.activeWrite !== void 0 ? " or draining provider write" : this.interrupting !== void 0 ? " or draining foreground interrupt" : ""}`, "SEND_ACTIVE");
|
|
456
458
|
if (request.signal?.aborted === true) throw new Error("PTY send aborted before write");
|
|
459
|
+
if (request.capture === true) this.capture = new BoundedTextBuffer(CAPTURE_MAX_BYTES);
|
|
457
460
|
const operation = new LocalSendOperation(this.config.maxReadBytes, Date.now(), () => {
|
|
458
461
|
this.interrupt(operation);
|
|
459
462
|
});
|
|
@@ -520,6 +523,7 @@ var LocalPtySession = class {
|
|
|
520
523
|
this.promptTail = "";
|
|
521
524
|
}
|
|
522
525
|
read(request) {
|
|
526
|
+
if (request.capture === true) return this.capture?.snapshot() ?? { text: "", truncated: false };
|
|
523
527
|
const snapshot = this.scrollback.snapshot();
|
|
524
528
|
const lines = snapshot.text.split("\n");
|
|
525
529
|
const totalLines = snapshot.text.length === 0 ? 0 : lines.length;
|
|
@@ -625,6 +629,7 @@ var LocalPtySession = class {
|
|
|
625
629
|
if (text.length === 0) return;
|
|
626
630
|
this.lastOutputAt = Date.now();
|
|
627
631
|
this.scrollback.append(text);
|
|
632
|
+
this.capture?.append(text);
|
|
628
633
|
this.active?.append(text);
|
|
629
634
|
}
|
|
630
635
|
schedulePoll(operation, delayMs = this.config.pollIntervalMs) {
|
package/vendor/tui/lib/app.mjs
CHANGED
|
@@ -38,6 +38,7 @@ import { dscodeFooterHeader } from './render/status.mjs';
|
|
|
38
38
|
import { dscodePadEnd, welcomeArtRows, welcomePath, WELCOME_ART, WELCOME_ART_SMALL } from './dscode/welcome.mjs';
|
|
39
39
|
import { TETRIS_TICK_MS as DSCODE_TETRIS_TICK_MS, TETRIS_WIDTH as DSCODE_TETRIS_WIDTH, tetrisFrame as dscodeTetrisFrame } from '../../../plugins/compaction/tetris.mjs';
|
|
40
40
|
import { PROVIDERS as DSCODE_PROVIDERS, providerSpec as dscodeProviderSpec, providerArgument as dscodeProviderArgument, providerOfLabel as dscodeProviderOfLabel, pickModel as dscodePickModel, credentialState as dscodeCredentialState, waitForModels as dscodeWaitForModels, } from '../../../plugins/providers/catalog.mjs';
|
|
41
|
+
import { grokStatusText } from '../../../plugins/grok/status.mjs';
|
|
41
42
|
import { ENV_ASSIGNMENT, hasWrappingQuotes } from './provider-settings.mjs';
|
|
42
43
|
import { collapseLargePaste, expandLargePastes, pasteAtomicEdit, pasteCursorEdge } from './dscode/paste.mjs';
|
|
43
44
|
import { loadFlag, saveFlag } from './dscode/flags.mjs';
|
|
@@ -2228,7 +2229,32 @@ export function DscodeLoginPanel({ provider, load, save, done, back }) {
|
|
|
2228
2229
|
});
|
|
2229
2230
|
return createElement(Box, { flexDirection: "column", paddingX: 2 }, createElement(Text, { bold: true }, spec.name + " login"), createElement(Text, { dimColor: true }, "Saved on this Mac in ~/.dscode/credentials.yaml"), createElement(Text, null, busy ? "Saving…" : target ? "API key › " + (draft ? "••••••••" : "paste your key") : error ? "" : "Loading…"), error ? createElement(Text, { color: "red" }, error) : void 0, createElement(Text, { dimColor: true }, "Enter save · Esc cancel · Ctrl+U clear"));
|
|
2230
2231
|
}
|
|
2231
|
-
|
|
2232
|
+
/**
|
|
2233
|
+
* dscode: the Grok rail has no key to paste — its token is the local `grok login`. This panel
|
|
2234
|
+
* reports that login and the weekly credit window, and names the command that fixes a missing
|
|
2235
|
+
* or expired one. `r` re-reads both, so a login finished in another terminal shows up here.
|
|
2236
|
+
*/
|
|
2237
|
+
export function DscodeGrokPanel({ snapshot, refresh, back }) {
|
|
2238
|
+
useStableInput((input, key) => {
|
|
2239
|
+
if (key.escape || key.ctrl && input === "c" || input === "q") {
|
|
2240
|
+
back();
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
if (input === "r")
|
|
2244
|
+
refresh();
|
|
2245
|
+
});
|
|
2246
|
+
const { status, subscription } = snapshot;
|
|
2247
|
+
const used = subscription?.usedPercent;
|
|
2248
|
+
const detail = status.kind === "ready"
|
|
2249
|
+
? "Local grok login detected" + (status.expiresIn === undefined ? "" : " · token expires in about " + status.expiresIn + " min")
|
|
2250
|
+
: status.kind === "expired" ? "The local grok login expired."
|
|
2251
|
+
: status.kind === "missing" ? "No local grok login found."
|
|
2252
|
+
: "The local grok login file is not readable.";
|
|
2253
|
+
return createElement(Box, { flexDirection: "column", paddingX: 2 }, createElement(Text, { bold: true }, "Grok subscription"), createElement(Text, null, detail), ...status.kind === "ready" ? [] : [createElement(Text, { key: "fix" }, "Run grok login in a terminal, then press r to re-check.")], createElement(Text, { key: "tier" }, "Plan: " + (subscription?.tier ?? "not reported")), createElement(Text, { key: "usage" }, used === undefined
|
|
2254
|
+
? "Weekly usage: the server reports none for this period"
|
|
2255
|
+
: "Weekly usage: " + (Number.isInteger(used) ? String(used) : used.toFixed(1)) + "% used"), ...subscription?.periodEnd === undefined ? [] : [createElement(Text, { key: "reset" }, "Resets: " + new Date(subscription.periodEnd).toLocaleString())], createElement(Text, { dimColor: true }, "r re-check · Esc close"));
|
|
2256
|
+
}
|
|
2257
|
+
export function DscodeProviderPanel({ current, load, choose, back, grokStatus }) {
|
|
2232
2258
|
const [directory, setDirectory] = useState(void 0);
|
|
2233
2259
|
const [failed, setFailed] = useState(false);
|
|
2234
2260
|
const [cursor, setCursor] = useState(() => Math.max(0, DSCODE_PROVIDERS.findIndex(provider => provider.id === current)));
|
|
@@ -2259,6 +2285,8 @@ export function DscodeProviderPanel({ current, load, choose, back }) {
|
|
|
2259
2285
|
}
|
|
2260
2286
|
});
|
|
2261
2287
|
const status = provider => {
|
|
2288
|
+
if (provider.id === "grok")
|
|
2289
|
+
return grokStatus?.() ?? "grok login not detected";
|
|
2262
2290
|
if (failed)
|
|
2263
2291
|
return "status unavailable";
|
|
2264
2292
|
if (directory === void 0)
|
|
@@ -5864,7 +5892,7 @@ export function App(props) {
|
|
|
5864
5892
|
if (state === 'missing') {
|
|
5865
5893
|
setProviderOpen(false);
|
|
5866
5894
|
setEffortFor(undefined);
|
|
5867
|
-
setProviderAction({ kind: 'dscode-key', provider, then: () => dscodeSwitchProvider(provider) });
|
|
5895
|
+
setProviderAction(provider === 'grok' ? { kind: 'dscode-grok' } : { kind: 'dscode-key', provider, then: () => dscodeSwitchProvider(provider) });
|
|
5868
5896
|
setModelOpen(true);
|
|
5869
5897
|
return;
|
|
5870
5898
|
}
|
|
@@ -5969,6 +5997,14 @@ export function App(props) {
|
|
|
5969
5997
|
current: dscodeProviderOfLabel(modelLabel),
|
|
5970
5998
|
load: props.loadModelProviders,
|
|
5971
5999
|
choose: dscodeSwitchProvider,
|
|
6000
|
+
grokStatus: () => grokStatusText(props.dscodeGrokStatus?.() ?? { status: { kind: "missing" } }),
|
|
6001
|
+
back: closeModelSurface,
|
|
6002
|
+
});
|
|
6003
|
+
}
|
|
6004
|
+
else if (providerAction?.kind === 'dscode-grok') {
|
|
6005
|
+
modelSurface = createElement(DscodeGrokPanel, {
|
|
6006
|
+
snapshot: props.dscodeGrokStatus?.() ?? { status: { kind: "missing" } },
|
|
6007
|
+
refresh: reloadModelSurfaces,
|
|
5972
6008
|
back: closeModelSurface,
|
|
5973
6009
|
});
|
|
5974
6010
|
}
|
|
@@ -6477,7 +6513,8 @@ export function App(props) {
|
|
|
6477
6513
|
openLogin: (provider) => {
|
|
6478
6514
|
setProviderOpen(false);
|
|
6479
6515
|
setEffortFor(undefined);
|
|
6480
|
-
|
|
6516
|
+
const target = provider ?? dscodeProviderOfLabel(modelLabel);
|
|
6517
|
+
setProviderAction(target === 'grok' ? { kind: 'dscode-grok' } : { kind: 'dscode-key', provider: target });
|
|
6481
6518
|
setModelOpen(true);
|
|
6482
6519
|
},
|
|
6483
6520
|
openOpenRouter: () => {
|
|
@@ -19,6 +19,20 @@ export function userBackgroundRows(rows, columns, measure) {
|
|
|
19
19
|
};
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
|
+
/** Local wall-clock HH:MM:SS for a row, or undefined when it carries no time. */
|
|
23
|
+
function timeStamp(time) {
|
|
24
|
+
if (time === undefined)
|
|
25
|
+
return undefined;
|
|
26
|
+
const at = new Date(time);
|
|
27
|
+
const pad = (value) => String(value).padStart(2, '0');
|
|
28
|
+
return pad(at.getHours()) + ':' + pad(at.getMinutes()) + ':' + pad(at.getSeconds());
|
|
29
|
+
}
|
|
30
|
+
/** Put a dim timestamp in front of a block first row. */
|
|
31
|
+
function stamped(lines, label) {
|
|
32
|
+
if (label === undefined || lines.length === 0)
|
|
33
|
+
return lines;
|
|
34
|
+
return [{ ...lines[0], segments: [lineSegment(label + ' ', 'dim'), ...lines[0].segments] }, ...lines.slice(1)];
|
|
35
|
+
}
|
|
22
36
|
/** The folded thinking tail: one dim paragraph, capped, with the Ctrl+O hint. */
|
|
23
37
|
function thinkingLines(reasoning, width) {
|
|
24
38
|
const lines = hangingTextLines('Thinking: ' + reasoning.replace(/\s+/g, ' ').trim(), width, '· ', 'dimItalic', ' ');
|
|
@@ -34,15 +48,21 @@ function thinkingLines(reasoning, width) {
|
|
|
34
48
|
*/
|
|
35
49
|
export function dscodeChatLines(entry, columns, verbose = false) {
|
|
36
50
|
const width = Math.max(1, Math.floor(columns));
|
|
51
|
+
const label = entry.kind === 'assistant' || entry.kind === 'tool' ? timeStamp(entry.time) : undefined;
|
|
52
|
+
// The stamp leads the block, so the rows wrap to what is left of the width: a row that
|
|
53
|
+
// overflowed here would cost an extra screen row per block and move the composer.
|
|
54
|
+
const stampWidth = label === undefined ? 0 : visibleColumns(label + ' ');
|
|
55
|
+
const blockWidth = Math.max(1, width - stampWidth);
|
|
56
|
+
const blockColumns = Math.max(1, columns - stampWidth);
|
|
37
57
|
if (entry.kind === 'tool') {
|
|
38
58
|
if (!verbose)
|
|
39
59
|
return [];
|
|
40
60
|
const state = entry.state === 'running' ? ' · running' : entry.state === 'error' ? ' · error' : '';
|
|
41
|
-
const lines = hangingStyledLines([
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
61
|
+
const lines = [...stamped(hangingStyledLines([
|
|
62
|
+
lineSegment('Tool Call: ' + entry.name, 'dim'),
|
|
63
|
+
lineSegment(entry.preview ? ' ' + entry.preview : '', 'dim'),
|
|
64
|
+
lineSegment(state, entry.state === 'error' ? 'error' : 'dim'),
|
|
65
|
+
], blockWidth, '· ', 'dim', ' ', 'dim'), label)];
|
|
46
66
|
if (entry.summary) {
|
|
47
67
|
lines.push(...hangingTextLines('Output: ' + entry.summary, width, ' ', entry.state === 'error' ? 'error' : 'dim', ' '));
|
|
48
68
|
}
|
|
@@ -50,10 +70,10 @@ export function dscodeChatLines(entry, columns, verbose = false) {
|
|
|
50
70
|
return lines;
|
|
51
71
|
}
|
|
52
72
|
if (entry.kind === 'assistant') {
|
|
53
|
-
const thinking = verbose && entry.reasoning ? [...thinkingLines(entry.reasoning,
|
|
73
|
+
const thinking = verbose && entry.reasoning ? [...stamped(thinkingLines(entry.reasoning, blockWidth), label), { segments: [] }] : [];
|
|
54
74
|
if (!entry.text && !entry.interrupted)
|
|
55
75
|
return thinking;
|
|
56
|
-
const body = [...thinking, ...transcriptEntryLines({ ...entry, reasoning: '' },
|
|
76
|
+
const body = [...thinking, ...stamped(transcriptEntryLines({ ...entry, reasoning: '' }, blockColumns, false, false, false), label)];
|
|
57
77
|
// The turn's last reply closes the turn with its own rule and breathing rows.
|
|
58
78
|
return entry.turnEnded === true
|
|
59
79
|
? [...body, { segments: [] }, { segments: [{ text: '─'.repeat(width), style: 'dim' }] }, { segments: [] }]
|
package/vendor/tui/lib/index.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import { isSlashLine, submissionPayload, watchCommands } from './commands.mjs';
|
|
|
25
25
|
import { internals } from './internals.mjs';
|
|
26
26
|
import { syncModelCapabilities } from './model-capabilities.mjs';
|
|
27
27
|
import { ensureProviderRoute as dscodeEnsureProviderRoute, migrateOpenRouterProfile as dscodeMigrateOpenRouter } from '../../../plugins/providers/catalog.mjs';
|
|
28
|
+
import { grokStatusSnapshot } from '../../../plugins/grok/status.mjs';
|
|
28
29
|
import { compactionPreview as dscodeCompactionPreview, pricedThresholdRatio as dscodePricedThresholdRatio } from '../../../plugins/compaction/threshold.mjs';
|
|
29
30
|
import { dscodeLoadOpenRouterAccountFor, dscodeManagementKeyStatus, dscodeSaveManagementKey } from './app.mjs';
|
|
30
31
|
import { buildModelSelection, applyModelSelectionToConfig, loadModelDirectory, modelSelectionLabel, pendingModelSelection, resolveEffectiveSelection } from './models.mjs';
|
|
@@ -2026,6 +2027,7 @@ async function run(ctx, startup, io) {
|
|
|
2026
2027
|
quit,
|
|
2027
2028
|
loadModels: () => dscodeMigrateOpenRouter(ctx.get('settings')).then(() => loadModelDirectory(ctx)),
|
|
2028
2029
|
dscodeEnsureProviderRoute: (provider) => dscodeEnsureProviderRoute(ctx.get('settings'), provider),
|
|
2030
|
+
dscodeGrokStatus: () => grokStatusSnapshot(),
|
|
2029
2031
|
dscodeManagementKeyStatus: () => dscodeManagementKeyStatus(ctx),
|
|
2030
2032
|
dscodeSaveManagementKey: (key) => dscodeSaveManagementKey(ctx, key),
|
|
2031
2033
|
dscodeLoadOpenRouterAccount: () => dscodeLoadOpenRouterAccountFor(ctx),
|
|
@@ -551,7 +551,7 @@ export function projectEvent(view, event) {
|
|
|
551
551
|
...view,
|
|
552
552
|
streaming: '',
|
|
553
553
|
streamingReasoning: '',
|
|
554
|
-
...(renderable ? { entries: [...view.entries, { kind: 'assistant', text, reasoning, interrupted: event.data.interrupted === true ? true : undefined }] } : {}),
|
|
554
|
+
...(renderable ? { entries: [...view.entries, { kind: 'assistant', text, reasoning, time: event.time, interrupted: event.data.interrupted === true ? true : undefined }] } : {}),
|
|
555
555
|
stats: {
|
|
556
556
|
...stats,
|
|
557
557
|
llmMs: stats.llmMs + (started === undefined ? 0 : Math.max(0, event.time - started)),
|
|
@@ -591,6 +591,7 @@ export function projectEvent(view, event) {
|
|
|
591
591
|
...view.entries,
|
|
592
592
|
{
|
|
593
593
|
kind: 'tool',
|
|
594
|
+
time: event.time,
|
|
594
595
|
callId: data.callId,
|
|
595
596
|
ordinal,
|
|
596
597
|
name: data.name,
|
|
@@ -1314,7 +1315,7 @@ export function replayProjectEvent(acc, event) {
|
|
|
1314
1315
|
// Same zero-line guard as the live fold: tool-only settlements carry
|
|
1315
1316
|
// timing/usage but no renderable transcript entry.
|
|
1316
1317
|
if (text !== '' || reasoning !== '' || event.data.interrupted === true) {
|
|
1317
|
-
appendReplayEntry(acc, { kind: 'assistant', text, reasoning, interrupted: event.data.interrupted === true ? true : undefined });
|
|
1318
|
+
appendReplayEntry(acc, { kind: 'assistant', text, reasoning, time: event.time, interrupted: event.data.interrupted === true ? true : undefined });
|
|
1318
1319
|
}
|
|
1319
1320
|
acc.stats = {
|
|
1320
1321
|
...acc.stats,
|
|
@@ -1346,6 +1347,7 @@ export function replayProjectEvent(acc, event) {
|
|
|
1346
1347
|
acc.toolCallOrdinal += 1;
|
|
1347
1348
|
appendReplayEntry(acc, {
|
|
1348
1349
|
kind: 'tool',
|
|
1350
|
+
time: event.time,
|
|
1349
1351
|
callId: data.callId,
|
|
1350
1352
|
ordinal: acc.toolCallOrdinal,
|
|
1351
1353
|
name: data.name,
|