@giovannijecha/jecode 0.8.1 → 0.8.2
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/dist/context/budget.js +15 -3
- package/dist/controller-request.js +19 -11
- package/dist/controller.js +20 -4
- package/dist/openai-oauth.js +59 -15
- package/dist/providers/ollama.js +26 -10
- package/dist/sessions/store.js +20 -10
- package/dist/tui/blocks.js +1 -3
- package/dist/tui/components/messages.js +9 -13
- package/dist/tui/components/tool.js +2 -4
- package/dist/tui/transcript-grammar.js +1 -1
- package/dist/ui/theme.js +18 -18
- package/package.json +1 -1
package/dist/context/budget.js
CHANGED
|
@@ -18,10 +18,17 @@ export function estimateRequestInputTokens(envelope) {
|
|
|
18
18
|
}
|
|
19
19
|
/** Clamp the configured output ceiling so the complete request remains usable. */
|
|
20
20
|
export function budgetRequest(envelope, configuredMaxOutputTokens, policy) {
|
|
21
|
-
|
|
22
|
-
throw new Error("max output tokens must be a positive safe integer");
|
|
23
|
-
}
|
|
21
|
+
requirePositiveInteger(configuredMaxOutputTokens, "max output tokens");
|
|
24
22
|
const inputTokens = estimateRequestInputTokens(envelope);
|
|
23
|
+
return finishBudget(inputTokens, configuredMaxOutputTokens, policy);
|
|
24
|
+
}
|
|
25
|
+
/** Reuse an exact estimate already computed for the same request envelope. */
|
|
26
|
+
export function budgetRequestFromInputTokens(inputTokens, configuredMaxOutputTokens, policy) {
|
|
27
|
+
requirePositiveInteger(inputTokens, "request input tokens");
|
|
28
|
+
requirePositiveInteger(configuredMaxOutputTokens, "max output tokens");
|
|
29
|
+
return finishBudget(inputTokens, configuredMaxOutputTokens, policy);
|
|
30
|
+
}
|
|
31
|
+
function finishBudget(inputTokens, configuredMaxOutputTokens, policy) {
|
|
25
32
|
const available = policy.requestLimitTokens - inputTokens;
|
|
26
33
|
const minimum = Math.min(configuredMaxOutputTokens, MIN_REQUEST_OUTPUT_TOKENS);
|
|
27
34
|
if (available < minimum) {
|
|
@@ -35,3 +42,8 @@ export function budgetRequest(envelope, configuredMaxOutputTokens, policy) {
|
|
|
35
42
|
limitTokens: policy.requestLimitTokens,
|
|
36
43
|
});
|
|
37
44
|
}
|
|
45
|
+
function requirePositiveInteger(value, label) {
|
|
46
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
47
|
+
throw new Error(`${label} must be a positive safe integer`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
// One streamed provider request with a single safe context-overflow recovery.
|
|
2
|
-
import {
|
|
2
|
+
import { budgetRequestFromInputTokens, estimateRequestInputTokens, } from "./context/budget.js";
|
|
3
3
|
import { isContextOverflow } from "./context/policy.js";
|
|
4
4
|
export async function requestAssistant(history, current, specs, options, events, signal) {
|
|
5
5
|
let policy = await options.contextPolicy();
|
|
6
6
|
const prepared = await prepareContext(history, current, specs, options, events, policy, "budget");
|
|
7
|
-
let context = prepared === undefined ? [...current] : clone(prepared);
|
|
7
|
+
let context = prepared.projected === undefined ? [...current] : clone(prepared.projected);
|
|
8
|
+
let inputTokens = prepared.inputTokens;
|
|
8
9
|
let recovered = false;
|
|
9
10
|
for (;;) {
|
|
10
|
-
const budget =
|
|
11
|
-
system: options.system,
|
|
12
|
-
messages: context,
|
|
13
|
-
tools: specs,
|
|
14
|
-
}, options.maxTokens, policy);
|
|
11
|
+
const budget = budgetRequestFromInputTokens(inputTokens, options.maxTokens, policy);
|
|
15
12
|
try {
|
|
16
13
|
const message = await options.provider.send({
|
|
17
14
|
model: options.model,
|
|
@@ -31,10 +28,11 @@ export async function requestAssistant(history, current, specs, options, events,
|
|
|
31
28
|
throw error;
|
|
32
29
|
if (isContextOverflow(error))
|
|
33
30
|
policy = await options.contextPolicy();
|
|
34
|
-
const
|
|
35
|
-
if (projected === undefined)
|
|
31
|
+
const next = await prepareContext(history, context, specs, options, events, policy, "overflow", error);
|
|
32
|
+
if (next.projected === undefined)
|
|
36
33
|
throw error;
|
|
37
|
-
context = clone(projected);
|
|
34
|
+
context = clone(next.projected);
|
|
35
|
+
inputTokens = next.inputTokens;
|
|
38
36
|
recovered = true;
|
|
39
37
|
}
|
|
40
38
|
}
|
|
@@ -45,12 +43,22 @@ async function prepareContext(history, context, specs, options, events, policy,
|
|
|
45
43
|
messages: context,
|
|
46
44
|
tools: specs,
|
|
47
45
|
});
|
|
48
|
-
|
|
46
|
+
const projected = await events.onContext?.(history, context, {
|
|
49
47
|
reason,
|
|
50
48
|
policy,
|
|
51
49
|
inputTokens,
|
|
52
50
|
...(error === undefined ? {} : { error }),
|
|
53
51
|
});
|
|
52
|
+
return {
|
|
53
|
+
projected,
|
|
54
|
+
inputTokens: projected === undefined
|
|
55
|
+
? inputTokens
|
|
56
|
+
: estimateRequestInputTokens({
|
|
57
|
+
system: options.system,
|
|
58
|
+
messages: projected,
|
|
59
|
+
tools: specs,
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
54
62
|
}
|
|
55
63
|
function clone(messages) {
|
|
56
64
|
return structuredClone([...messages]);
|
package/dist/controller.js
CHANGED
|
@@ -97,13 +97,29 @@ export async function runTurn(history, options, events, signal, modelHistory = h
|
|
|
97
97
|
events.onToolCall(call, preview);
|
|
98
98
|
prepared.push({ call, current, preview });
|
|
99
99
|
}
|
|
100
|
-
const
|
|
101
|
-
|
|
100
|
+
const settlements = await Promise.allSettled(prepared.map(({ call, current, preview }) => settle(call, current, calls.length, options, events, signal, preview)));
|
|
101
|
+
let batchFailure;
|
|
102
|
+
for (let offset = 0; offset < settlements.length; offset++) {
|
|
102
103
|
const call = prepared[offset]?.call;
|
|
103
|
-
const
|
|
104
|
+
const settlement = settlements[offset];
|
|
105
|
+
const run = settlement.status === "fulfilled"
|
|
106
|
+
? settlement.value
|
|
107
|
+
: refuse(call, signal?.aborted === true
|
|
108
|
+
? "interrupted before completion"
|
|
109
|
+
: "tool processing stopped before completion", signal?.aborted === true ? "interrupted" : "failed");
|
|
110
|
+
if (settlement.status === "rejected" && batchFailure === undefined) {
|
|
111
|
+
batchFailure = { error: settlement.reason };
|
|
112
|
+
}
|
|
104
113
|
results.push(run.result);
|
|
105
|
-
|
|
114
|
+
try {
|
|
115
|
+
events.onToolResult(call, run.result, run.summary);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
batchFailure ??= { error };
|
|
119
|
+
}
|
|
106
120
|
}
|
|
121
|
+
if (batchFailure !== undefined)
|
|
122
|
+
throw batchFailure.error;
|
|
107
123
|
}
|
|
108
124
|
}
|
|
109
125
|
catch (error) {
|
package/dist/openai-oauth.js
CHANGED
|
@@ -17,6 +17,8 @@ const DEVICE_POLL = `${AUTHORITY}/api/accounts/deviceauth/token`;
|
|
|
17
17
|
const DEVICE_VERIFY = `${AUTHORITY}/codex/device`;
|
|
18
18
|
const DEVICE_REDIRECT = `${AUTHORITY}/deviceauth/callback`;
|
|
19
19
|
const LOGIN_LIMIT_MS = 15 * 60_000;
|
|
20
|
+
/** RFC 8628 increases the polling interval by five seconds after `slow_down`. */
|
|
21
|
+
const SLOW_DOWN_INCREMENT_MS = 5_000;
|
|
20
22
|
export async function beginBrowserLogin() {
|
|
21
23
|
const verifier = randomBytes(64).toString("base64url");
|
|
22
24
|
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
@@ -121,23 +123,65 @@ async function exchange(code, signal) {
|
|
|
121
123
|
return openAITokenReply(response.value);
|
|
122
124
|
}
|
|
123
125
|
async function pollDevice(deviceAuthId, userCode, interval, signal) {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
126
|
+
const deadline = new AbortController();
|
|
127
|
+
const timer = setTimeout(() => {
|
|
128
|
+
deadline.abort(new Error("ChatGPT device sign-in timed out after 15 minutes"));
|
|
129
|
+
}, LOGIN_LIMIT_MS);
|
|
130
|
+
const combined = signal === undefined
|
|
131
|
+
? deadline.signal
|
|
132
|
+
: AbortSignal.any([signal, deadline.signal]);
|
|
133
|
+
let intervalMs = interval * 1_000;
|
|
134
|
+
try {
|
|
135
|
+
while (true) {
|
|
136
|
+
const response = await oauthRequest(DEVICE_POLL, {
|
|
137
|
+
contentType: "application/json",
|
|
138
|
+
value: { device_auth_id: deviceAuthId, user_code: userCode },
|
|
139
|
+
}, combined, [200, 400, 403, 404, 429]);
|
|
140
|
+
if (combined.aborted)
|
|
141
|
+
throw abortReason(combined);
|
|
142
|
+
if (response.status === 200) {
|
|
143
|
+
const value = record(response.value) ? response.value : {};
|
|
144
|
+
return {
|
|
145
|
+
authorizationCode: required(value["authorization_code"], "authorization code"),
|
|
146
|
+
verifier: required(value["code_verifier"], "code verifier"),
|
|
147
|
+
redirectUri: DEVICE_REDIRECT,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const errorCode = deviceErrorCode(response.value);
|
|
151
|
+
if (errorCode === "access_denied") {
|
|
152
|
+
throw new Error("ChatGPT device sign-in was denied");
|
|
153
|
+
}
|
|
154
|
+
if (errorCode === "expired_token") {
|
|
155
|
+
throw new Error("ChatGPT device sign-in code expired");
|
|
156
|
+
}
|
|
157
|
+
const pending = errorCode === "authorization_pending" ||
|
|
158
|
+
errorCode === "deviceauth_authorization_pending" ||
|
|
159
|
+
((response.status === 403 || response.status === 404) && errorCode === undefined);
|
|
160
|
+
const slowDown = errorCode === "slow_down" ||
|
|
161
|
+
(response.status === 429 && errorCode === undefined);
|
|
162
|
+
if (!pending && !slowDown) {
|
|
163
|
+
throw new Error(`ChatGPT device sign-in failed (${response.status})`);
|
|
164
|
+
}
|
|
165
|
+
if (slowDown)
|
|
166
|
+
intervalMs += SLOW_DOWN_INCREMENT_MS;
|
|
167
|
+
await sleep(intervalMs, combined);
|
|
137
168
|
}
|
|
138
|
-
await sleep(interval * 1_000, signal);
|
|
139
169
|
}
|
|
140
|
-
|
|
170
|
+
finally {
|
|
171
|
+
clearTimeout(timer);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function deviceErrorCode(value) {
|
|
175
|
+
if (!record(value))
|
|
176
|
+
return undefined;
|
|
177
|
+
const error = value["error"];
|
|
178
|
+
const nested = typeof error === "string"
|
|
179
|
+
? error
|
|
180
|
+
: record(error)
|
|
181
|
+
? optional(error["code"])
|
|
182
|
+
: undefined;
|
|
183
|
+
const code = nested ?? optional(value["code"]);
|
|
184
|
+
return code?.trim().toLowerCase();
|
|
141
185
|
}
|
|
142
186
|
function intervalSeconds(value) {
|
|
143
187
|
const parsed = typeof value === "string" ? Number(value.trim()) : value;
|
package/dist/providers/ollama.js
CHANGED
|
@@ -17,7 +17,8 @@ const KEY = "OLLAMA_API_KEY";
|
|
|
17
17
|
const OLLAMA_EFFORTS = ["low", "medium", "high"];
|
|
18
18
|
let configuredHost;
|
|
19
19
|
const CONTEXT_CACHE_MS = 30_000;
|
|
20
|
-
const
|
|
20
|
+
const runtimeContextByEndpoint = new Map();
|
|
21
|
+
const modelContextByEndpoint = new Map();
|
|
21
22
|
/** Set the endpoint selected for this process. Undefined restores key-aware inference. */
|
|
22
23
|
export function configureOllama(host) {
|
|
23
24
|
configuredHost = host === undefined ? undefined : parseOllamaEndpoint(host).baseUrl;
|
|
@@ -56,15 +57,16 @@ export const ollama = {
|
|
|
56
57
|
async contextWindow(model, signal, onStatus) {
|
|
57
58
|
const at = endpoint();
|
|
58
59
|
const cacheKey = `${at.baseUrl}\u0000${model}`;
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
return
|
|
62
|
-
const
|
|
60
|
+
const runtime = cachedContext(runtimeContextByEndpoint, cacheKey);
|
|
61
|
+
if (runtime !== undefined)
|
|
62
|
+
return runtime;
|
|
63
|
+
const modelCapacity = cachedContext(modelContextByEndpoint, cacheKey);
|
|
64
|
+
const observed = await nativeContextWindow(at, model, modelCapacity, signal, onStatus);
|
|
63
65
|
if (observed?.runtime === true) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
rememberContext(runtimeContextByEndpoint, cacheKey, observed.value);
|
|
67
|
+
}
|
|
68
|
+
else if (observed !== undefined && modelCapacity === undefined) {
|
|
69
|
+
rememberContext(modelContextByEndpoint, cacheKey, observed.value);
|
|
68
70
|
}
|
|
69
71
|
return observed?.value;
|
|
70
72
|
},
|
|
@@ -96,7 +98,7 @@ export const ollama = {
|
|
|
96
98
|
return fromWireReply(reply);
|
|
97
99
|
},
|
|
98
100
|
};
|
|
99
|
-
async function nativeContextWindow(at, model, signal, onStatus) {
|
|
101
|
+
async function nativeContextWindow(at, model, fallback, signal, onStatus) {
|
|
100
102
|
try {
|
|
101
103
|
const running = await getJson(`${at.baseUrl}/api/ps`, headers(at), signal, onStatus);
|
|
102
104
|
const allocated = runningContext(running, model);
|
|
@@ -106,6 +108,8 @@ async function nativeContextWindow(at, model, signal, onStatus) {
|
|
|
106
108
|
catch (error) {
|
|
107
109
|
throwIfAborted(signal, error);
|
|
108
110
|
}
|
|
111
|
+
if (fallback !== undefined)
|
|
112
|
+
return { value: fallback, runtime: false };
|
|
109
113
|
try {
|
|
110
114
|
const details = await postJson(`${at.baseUrl}/api/show`, headers(at), { model }, signal, onStatus);
|
|
111
115
|
const capacity = modelCapacity(details);
|
|
@@ -118,6 +122,18 @@ async function nativeContextWindow(at, model, signal, onStatus) {
|
|
|
118
122
|
return undefined;
|
|
119
123
|
}
|
|
120
124
|
}
|
|
125
|
+
function cachedContext(cache, key) {
|
|
126
|
+
const cached = cache.get(key);
|
|
127
|
+
if (cached === undefined)
|
|
128
|
+
return undefined;
|
|
129
|
+
if (cached.expiresAt > Date.now())
|
|
130
|
+
return cached.value;
|
|
131
|
+
cache.delete(key);
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
function rememberContext(cache, key, value) {
|
|
135
|
+
cache.set(key, { value, expiresAt: Date.now() + CONTEXT_CACHE_MS });
|
|
136
|
+
}
|
|
121
137
|
function runningContext(value, model) {
|
|
122
138
|
if (!record(value) || !Array.isArray(value["models"]))
|
|
123
139
|
return undefined;
|
package/dist/sessions/store.js
CHANGED
|
@@ -16,6 +16,7 @@ const DIRECTORY_MODE = 0o700;
|
|
|
16
16
|
const FILE_MODE = 0o600;
|
|
17
17
|
const MAX_CATALOG_ENTRIES = 4_096;
|
|
18
18
|
const CATALOG_READ_CONCURRENCY = 8;
|
|
19
|
+
const NODE_READ_CONCURRENCY = 4;
|
|
19
20
|
const SESSION_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
|
|
20
21
|
const NODE_NAME = /^(\d{6})\.json$/;
|
|
21
22
|
const ATOMIC_NODE_TEMP = /^\.\d{6}\.json\.\d+\.[a-f0-9-]+\.tmp$/;
|
|
@@ -296,17 +297,26 @@ async function readNodes(directory) {
|
|
|
296
297
|
}
|
|
297
298
|
const stored = [];
|
|
298
299
|
const sequences = new Set();
|
|
299
|
-
for (let
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
300
|
+
for (let start = 0; start < names.length; start += NODE_READ_CONCURRENCY) {
|
|
301
|
+
const decoded = await Promise.all(names.slice(start, start + NODE_READ_CONCURRENCY)
|
|
302
|
+
.map(async (name, offset) => {
|
|
303
|
+
const id = Number(NODE_NAME.exec(name)?.[1]);
|
|
304
|
+
if (id !== start + offset + 1) {
|
|
305
|
+
throw new Error("session conversation nodes are not contiguous");
|
|
306
|
+
}
|
|
307
|
+
const entry = decodeNode(await readJson(path.join(directory, name), SESSION_FILE_LIMITS.nodeBytes));
|
|
308
|
+
if (entry.node.id !== id) {
|
|
309
|
+
throw new Error("session conversation node identity is invalid");
|
|
310
|
+
}
|
|
311
|
+
return entry;
|
|
312
|
+
}));
|
|
313
|
+
for (const entry of decoded) {
|
|
314
|
+
if (sequences.has(entry.sequence)) {
|
|
315
|
+
throw new Error("session conversation node identity is invalid");
|
|
316
|
+
}
|
|
317
|
+
sequences.add(entry.sequence);
|
|
318
|
+
stored.push(entry);
|
|
307
319
|
}
|
|
308
|
-
sequences.add(decoded.sequence);
|
|
309
|
-
stored.push(decoded);
|
|
310
320
|
}
|
|
311
321
|
return stored;
|
|
312
322
|
}
|
package/dist/tui/blocks.js
CHANGED
|
@@ -11,12 +11,10 @@ export function render(block, width, pal, context = {}) {
|
|
|
11
11
|
case "reasoning":
|
|
12
12
|
return renderReasoning(block, width, pal, {
|
|
13
13
|
continues: context.previous?.kind === "reasoning",
|
|
14
|
-
followsTool: context.previous?.kind === "tool",
|
|
15
14
|
});
|
|
16
15
|
case "tool":
|
|
17
16
|
return renderTool(block, width, pal, {
|
|
18
|
-
continues: context.previous?.kind === "tool"
|
|
19
|
-
followsReasoning: context.previous?.kind === "reasoning",
|
|
17
|
+
continues: context.previous?.kind === "tool",
|
|
20
18
|
now: context.now,
|
|
21
19
|
motion: context.motion,
|
|
22
20
|
reducedMotion: context.reducedMotion,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { trailingText } from "../../text-boundary.js";
|
|
2
|
-
import { row } from "../../ui/render.js";
|
|
2
|
+
import { blank, row } from "../../ui/render.js";
|
|
3
3
|
import { markdown } from "../../ui/markdown.js";
|
|
4
|
-
import { transcriptLead,
|
|
4
|
+
import { transcriptLead, transcriptWidth } from "../transcript-grammar.js";
|
|
5
5
|
export const REASONING_PREVIEW_ROWS = 3;
|
|
6
6
|
const MIN_REASONING_PREVIEW_CHARS = 4_096;
|
|
7
7
|
const REASONING_PREVIEW_OVERSCAN = 12;
|
|
@@ -10,12 +10,14 @@ export function renderUser(block, width, pal) {
|
|
|
10
10
|
const content = markdown(block.text, inner, pal, inner);
|
|
11
11
|
return [
|
|
12
12
|
"",
|
|
13
|
+
blank(width, pal.surface.subtle),
|
|
13
14
|
...content.map((line, index) => row(width, [
|
|
14
15
|
...transcriptLead(width, index === 0
|
|
15
16
|
? { text: "❯", fg: pal.accent, bold: true }
|
|
16
17
|
: undefined),
|
|
17
18
|
...line.segs,
|
|
18
|
-
])),
|
|
19
|
+
], [], pal.surface.subtle)),
|
|
20
|
+
blank(width, pal.surface.subtle),
|
|
19
21
|
];
|
|
20
22
|
}
|
|
21
23
|
export function renderAnswer(block, width, pal) {
|
|
@@ -25,23 +27,17 @@ export function renderAnswer(block, width, pal) {
|
|
|
25
27
|
];
|
|
26
28
|
}
|
|
27
29
|
export function renderReasoning(block, width, pal, context = {}) {
|
|
28
|
-
const inner = transcriptWidth(width);
|
|
29
30
|
// Expanding a live stream is deferred until it is sealed. Re-parsing an
|
|
30
31
|
// ever-growing full thought on every token makes the whole TUI stall.
|
|
31
32
|
const expanded = block.expanded === true && block.live !== true;
|
|
32
33
|
const source = !expanded
|
|
33
|
-
? reasoningPreviewSource(block.text,
|
|
34
|
+
? reasoningPreviewSource(block.text, width)
|
|
34
35
|
: { text: block.text, truncated: false };
|
|
35
|
-
const content = markdown(source.text,
|
|
36
|
+
const content = markdown(source.text, width, pal, width);
|
|
36
37
|
const visible = expanded ? content : content.slice(-REASONING_PREVIEW_ROWS);
|
|
37
38
|
return [
|
|
38
|
-
...(context.
|
|
39
|
-
|
|
40
|
-
: context.continues === true ? [] : [""]),
|
|
41
|
-
...visible.map((line) => row(width, [
|
|
42
|
-
...transcriptLead(width, { text: "│", fg: pal.rule }),
|
|
43
|
-
...line.segs.map((seg) => ({ ...seg, fg: pal.ink.dim, italic: true })),
|
|
44
|
-
])),
|
|
39
|
+
...(context.continues === true ? [] : [""]),
|
|
40
|
+
...visible.map((line) => row(width, line.segs.map((seg) => ({ ...seg, fg: pal.ink.dim, italic: true })))),
|
|
45
41
|
];
|
|
46
42
|
}
|
|
47
43
|
export function reasoningPreviewSource(text, width) {
|
|
@@ -4,7 +4,7 @@ import { fitSegs, hasColor, plainLen, row } from "../../ui/render.js";
|
|
|
4
4
|
import { graphemeCeiling, graphemeFloor } from "../../text-boundary.js";
|
|
5
5
|
import { toolDuration } from "../../duration.js";
|
|
6
6
|
import { breathe, easeInOut, easeOut, interval, mix, TOOL_BIRTH_MS, TOOL_LEADER_MAX_MS, TOOL_ROW_ARRIVAL_MS, } from "../motion.js";
|
|
7
|
-
import { transcriptLead
|
|
7
|
+
import { transcriptLead } from "../transcript-grammar.js";
|
|
8
8
|
const OUTPUT_ROWS = 8;
|
|
9
9
|
const LIVE_OUTPUT_ROWS = 6;
|
|
10
10
|
const DIFF_ROWS = 15;
|
|
@@ -25,9 +25,7 @@ export function renderTool(block, width, pal, context = {}) {
|
|
|
25
25
|
const right = resultSegments(block, pal, context, now);
|
|
26
26
|
const leader = movingLeader(width, left, right, pal, context, now);
|
|
27
27
|
return [
|
|
28
|
-
...(context.
|
|
29
|
-
? [row(width, transcriptMark(width, { text: "│", fg: pal.rule }))]
|
|
30
|
-
: context.continues === true ? [] : [""]),
|
|
28
|
+
...(context.continues === true ? [] : [""]),
|
|
31
29
|
row(width, leader === undefined ? left : [...left, leader], right),
|
|
32
30
|
...shown.map(({ detail, sourceIndex }) => renderDetail(detail, block.tone, width, pal, context.reducedMotion === true ? undefined : context.motion?.rowsAt[sourceIndex ?? -1], now)),
|
|
33
31
|
];
|
package/dist/ui/theme.js
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
// Jecode's fixed terminal identity. Components depend on semantic tokens, not
|
|
2
2
|
// literal colours, while the product exposes one deliberate dark Slate look.
|
|
3
|
-
// Jecode's fixed dark Slate baseline.
|
|
4
|
-
//
|
|
5
|
-
// live state
|
|
3
|
+
// Jecode's fixed dark Slate baseline. Clear text contrast and luminous
|
|
4
|
+
// structural colours keep long terminal transcripts readable without making
|
|
5
|
+
// live state decorative.
|
|
6
6
|
// Components depend on these roles rather than embedding presentation values.
|
|
7
7
|
export const STEEL = {
|
|
8
|
-
accent: [
|
|
9
|
-
technical: [
|
|
10
|
-
focus: [
|
|
11
|
-
rule: [
|
|
8
|
+
accent: [102, 155, 210],
|
|
9
|
+
technical: [78, 201, 232],
|
|
10
|
+
focus: [102, 155, 210],
|
|
11
|
+
rule: [53, 80, 110],
|
|
12
12
|
ink: {
|
|
13
|
-
fg: [
|
|
14
|
-
bright: [
|
|
15
|
-
muted: [
|
|
16
|
-
dim: [
|
|
17
|
-
attention: [
|
|
18
|
-
added: [
|
|
19
|
-
removed: [
|
|
13
|
+
fg: [220, 224, 229],
|
|
14
|
+
bright: [235, 239, 244],
|
|
15
|
+
muted: [156, 169, 183],
|
|
16
|
+
dim: [112, 124, 137],
|
|
17
|
+
attention: [230, 191, 95],
|
|
18
|
+
added: [134, 203, 146],
|
|
19
|
+
removed: [232, 112, 112],
|
|
20
20
|
},
|
|
21
21
|
surface: {
|
|
22
|
-
subtle: [
|
|
23
|
-
added: [
|
|
24
|
-
removed: [
|
|
25
|
-
attention: [
|
|
22
|
+
subtle: [31, 38, 47],
|
|
23
|
+
added: [22, 55, 34],
|
|
24
|
+
removed: [62, 24, 27],
|
|
25
|
+
attention: [62, 50, 19],
|
|
26
26
|
},
|
|
27
27
|
};
|