@oh-my-pi/pi-coding-agent 16.1.1 → 16.1.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/CHANGELOG.md +22 -1
- package/dist/cli.js +3314 -3338
- package/dist/types/cli/bench-cli.d.ts +2 -1
- package/dist/types/config/settings-schema.d.ts +1 -1
- package/dist/types/main.d.ts +2 -0
- package/dist/types/modes/components/assistant-message.d.ts +12 -0
- package/dist/types/modes/components/welcome.d.ts +1 -1
- package/dist/types/sdk.d.ts +19 -2
- package/dist/types/session/auth-broker-config.d.ts +33 -6
- package/dist/types/system-prompt.d.ts +5 -1
- package/dist/types/task/executor.d.ts +10 -0
- package/dist/types/tools/find.d.ts +0 -2
- package/dist/types/tools/search.d.ts +3 -3
- package/package.json +12 -12
- package/scripts/measure-prompt-tokens.ts +63 -0
- package/src/cli/bench-cli.ts +64 -3
- package/src/cli/startup-cwd.ts +3 -13
- package/src/config/settings-schema.ts +1 -1
- package/src/cursor.ts +1 -1
- package/src/debug/raw-sse-buffer.ts +31 -10
- package/src/eval/py/prelude.py +1 -1
- package/src/export/html/tool-views.generated.js +1 -1
- package/src/extensibility/extensions/runner.ts +8 -2
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/main.ts +29 -9
- package/src/modes/components/assistant-message.ts +86 -0
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/welcome.ts +86 -8
- package/src/modes/controllers/event-controller.ts +1 -1
- package/src/prompts/system/personalities/default.md +8 -16
- package/src/prompts/system/system-prompt.md +101 -115
- package/src/prompts/tools/ast-edit.md +10 -12
- package/src/prompts/tools/ast-grep.md +14 -18
- package/src/prompts/tools/bash.md +19 -21
- package/src/prompts/tools/browser.md +24 -24
- package/src/prompts/tools/checkpoint.md +0 -1
- package/src/prompts/tools/debug.md +11 -15
- package/src/prompts/tools/eval.md +27 -27
- package/src/prompts/tools/find.md +6 -10
- package/src/prompts/tools/github.md +11 -15
- package/src/prompts/tools/goal.md +0 -7
- package/src/prompts/tools/inspect-image.md +0 -1
- package/src/prompts/tools/irc.md +15 -24
- package/src/prompts/tools/job.md +5 -8
- package/src/prompts/tools/learn.md +2 -2
- package/src/prompts/tools/lsp.md +27 -30
- package/src/prompts/tools/manage-skill.md +4 -4
- package/src/prompts/tools/read.md +21 -23
- package/src/prompts/tools/replace.md +0 -1
- package/src/prompts/tools/resolve.md +4 -9
- package/src/prompts/tools/rewind.md +1 -1
- package/src/prompts/tools/search.md +8 -10
- package/src/prompts/tools/task.md +33 -38
- package/src/prompts/tools/todo.md +14 -18
- package/src/prompts/tools/web-search.md +0 -4
- package/src/prompts/tools/write.md +1 -1
- package/src/sdk.ts +49 -102
- package/src/session/agent-session.ts +17 -2
- package/src/session/auth-broker-config.ts +36 -76
- package/src/session/session-history-format.ts +1 -1
- package/src/session/session-manager.ts +33 -6
- package/src/system-prompt.ts +28 -8
- package/src/task/executor.ts +57 -0
- package/src/task/index.ts +15 -1
- package/src/tools/browser.ts +1 -1
- package/src/tools/eval.ts +1 -1
- package/src/tools/find.ts +4 -17
- package/src/tools/memory-edit.ts +1 -1
- package/src/tools/search.ts +5 -5
package/src/main.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { EventLoopKeepalive } from "@oh-my-pi/pi-agent-core";
|
|
|
11
11
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
12
12
|
import {
|
|
13
13
|
$env,
|
|
14
|
+
directoryExists,
|
|
14
15
|
getLogPath,
|
|
15
16
|
getProjectDir,
|
|
16
17
|
logger,
|
|
@@ -575,7 +576,11 @@ async function moveMissingCwdSessionIfNeeded(
|
|
|
575
576
|
return { status: "declined" };
|
|
576
577
|
}
|
|
577
578
|
|
|
578
|
-
|
|
579
|
+
// Open anchored at the (now-missing) recorded cwd: `open` otherwise falls back
|
|
580
|
+
// to the launch cwd, which would make the `moveTo` below a no-op whenever the
|
|
581
|
+
// move target equals the current project dir. moveTo never chdirs, so the
|
|
582
|
+
// stale cwd is only a relocation source, not a directory we enter.
|
|
583
|
+
const manager = await SessionManager.open(session.path, sessionDir, undefined, { initialCwd: sourceCwd });
|
|
579
584
|
await manager.moveTo(cwd, sessionDir);
|
|
580
585
|
return { status: "moved", manager };
|
|
581
586
|
}
|
|
@@ -751,6 +756,20 @@ function discoverAppendSystemPromptFile(): string | undefined {
|
|
|
751
756
|
return undefined;
|
|
752
757
|
}
|
|
753
758
|
|
|
759
|
+
/** Apply resolved CLI/discovered prompt files without bypassing system prompt templates. */
|
|
760
|
+
export function applyResolvedSystemPromptInputs(
|
|
761
|
+
options: CreateAgentSessionOptions,
|
|
762
|
+
resolvedSystemPrompt: string | undefined,
|
|
763
|
+
resolvedAppendPrompt: string | undefined,
|
|
764
|
+
): void {
|
|
765
|
+
if (resolvedSystemPrompt) {
|
|
766
|
+
options.customSystemPrompt = resolvedSystemPrompt;
|
|
767
|
+
}
|
|
768
|
+
if (resolvedAppendPrompt) {
|
|
769
|
+
options.appendSystemPrompt = resolvedAppendPrompt;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
754
773
|
async function buildSessionOptions(
|
|
755
774
|
parsed: Args,
|
|
756
775
|
scopedModels: ScopedModel[],
|
|
@@ -875,13 +894,7 @@ async function buildSessionOptions(
|
|
|
875
894
|
// (handled by caller before createAgentSession)
|
|
876
895
|
|
|
877
896
|
// System prompt
|
|
878
|
-
|
|
879
|
-
options.systemPrompt = defaultPrompt => [resolvedSystemPrompt, resolvedAppendPrompt, ...defaultPrompt.slice(1)];
|
|
880
|
-
} else if (resolvedSystemPrompt) {
|
|
881
|
-
options.systemPrompt = defaultPrompt => [resolvedSystemPrompt, ...defaultPrompt.slice(1)];
|
|
882
|
-
} else if (resolvedAppendPrompt) {
|
|
883
|
-
options.systemPrompt = defaultPrompt => [...defaultPrompt, resolvedAppendPrompt];
|
|
884
|
-
}
|
|
897
|
+
applyResolvedSystemPromptInputs(options, resolvedSystemPrompt, resolvedAppendPrompt);
|
|
885
898
|
|
|
886
899
|
// Tools
|
|
887
900
|
if (parsed.noTools) {
|
|
@@ -1141,7 +1154,14 @@ export async function runRootCommand(
|
|
|
1141
1154
|
// Resuming a session from another project: switch the process into that
|
|
1142
1155
|
// project's directory and refresh cwd-derived caches before the session is
|
|
1143
1156
|
// built, so settings discovery, plugins, and capabilities all scope to it.
|
|
1144
|
-
|
|
1157
|
+
// Skip the chdir when the recorded project directory is gone: `setProjectDir`
|
|
1158
|
+
// would throw on the missing path. `SessionManager.open` then falls back to
|
|
1159
|
+
// the launch cwd, so the resumed session simply stays where the user is.
|
|
1160
|
+
if (
|
|
1161
|
+
selected.cwd &&
|
|
1162
|
+
normalizePathForComparison(selected.cwd) !== normalizePathForComparison(getProjectDir()) &&
|
|
1163
|
+
(await directoryExists(selected.cwd))
|
|
1164
|
+
) {
|
|
1145
1165
|
// Let the original (launch-cwd) plugin-root preload settle first so its
|
|
1146
1166
|
// late resolution can't clobber the re-warm we trigger below.
|
|
1147
1167
|
await pluginPreloadPromise.catch(() => {});
|
|
@@ -16,6 +16,59 @@ import { type CacheInvalidation, CacheInvalidationMarkerComponent } from "./cach
|
|
|
16
16
|
*/
|
|
17
17
|
const MAX_TRANSCRIPT_ERROR_LINES = 8;
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* A GFM table delimiter row (`| --- | :--: |`, with or without bounding pipes).
|
|
21
|
+
* The header row alone does not render a table — this delimiter is what makes
|
|
22
|
+
* Markdown lay one out, and a streaming table re-aligns its columns as rows
|
|
23
|
+
* arrive. Requires at least one column pipe so a bare thematic break (`---`)
|
|
24
|
+
* does not match.
|
|
25
|
+
*/
|
|
26
|
+
const MARKDOWN_TABLE_DELIMITER = /^ {0,3}\|?(?:[ \t]*:?-+:?[ \t]*\|)+[ \t]*:?-*:?[ \t]*$/;
|
|
27
|
+
|
|
28
|
+
/** Opening or closing fence of a code block: ≥3 backticks/tildes plus info string. */
|
|
29
|
+
const CODE_FENCE_LINE = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether `text` currently contains reflowing Markdown whose layout is not yet
|
|
33
|
+
* permanent: an open ` ```mermaid ` fence (the diagram reshapes as source
|
|
34
|
+
* arrives) or a GFM table (columns re-align as rows arrive). Used by
|
|
35
|
+
* {@link AssistantMessageComponent.isTranscriptBlockCommitStable}.
|
|
36
|
+
*
|
|
37
|
+
* Fence-aware: a mermaid block is detected by its opener, and table delimiters
|
|
38
|
+
* inside ordinary fenced code (shell pipes, ASCII separators, doc examples) are
|
|
39
|
+
* ignored so a long streamed code block is never held out of native scrollback.
|
|
40
|
+
* A delimiter counts only directly under a pipe-bearing header row, outside any
|
|
41
|
+
* code fence.
|
|
42
|
+
*/
|
|
43
|
+
function detectLiveReflowingMarkdown(text: string): boolean {
|
|
44
|
+
let fence: string | null = null;
|
|
45
|
+
let prevLine = "";
|
|
46
|
+
for (const line of text.split("\n")) {
|
|
47
|
+
const fenceMatch = CODE_FENCE_LINE.exec(line);
|
|
48
|
+
if (fence !== null) {
|
|
49
|
+
// Inside a code block: only a bare matching closing fence ends it.
|
|
50
|
+
if (
|
|
51
|
+
fenceMatch &&
|
|
52
|
+
fenceMatch[2]!.trim() === "" &&
|
|
53
|
+
fenceMatch[1]![0] === fence[0] &&
|
|
54
|
+
fenceMatch[1]!.length >= fence.length
|
|
55
|
+
) {
|
|
56
|
+
fence = null;
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (fenceMatch) {
|
|
61
|
+
if (/^mermaid\b/.test(fenceMatch[2]!.trim())) return true;
|
|
62
|
+
fence = fenceMatch[1]!;
|
|
63
|
+
prevLine = "";
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (prevLine.includes("|") && MARKDOWN_TABLE_DELIMITER.test(line)) return true;
|
|
67
|
+
prevLine = line;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
19
72
|
/**
|
|
20
73
|
* Frames for the streaming "thinking" pulse rendered in place of a hidden
|
|
21
74
|
* thinking block while the model is still producing it. A single fixed-width
|
|
@@ -36,6 +89,15 @@ export class AssistantMessageComponent extends Container {
|
|
|
36
89
|
#convertedKittyImages = new Map<string, ImageContent>();
|
|
37
90
|
#kittyConversionsInFlight = new Set<string>();
|
|
38
91
|
#transcriptBlockFinalized: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* True while a non-finalized text item carries reflowing Markdown — a
|
|
94
|
+
* ` ```mermaid ` fence or a GFM table — whose layout re-flows every frame as
|
|
95
|
+
* source arrives (a diagram reshaping, a table re-aligning its columns), so
|
|
96
|
+
* no prefix is byte-stable until the message finalizes. See
|
|
97
|
+
* {@link isTranscriptBlockCommitStable}. Recomputed in {@link updateContent}
|
|
98
|
+
* ahead of the fast-path return, so it tracks every stream tick.
|
|
99
|
+
*/
|
|
100
|
+
#hasLiveReflowingMarkdown = false;
|
|
39
101
|
/**
|
|
40
102
|
* When true, the turn-ending `Error: …` line for `stopReason === "error"` is
|
|
41
103
|
* suppressed because the same error is currently shown in the pinned banner
|
|
@@ -192,6 +254,21 @@ export class AssistantMessageComponent extends Container {
|
|
|
192
254
|
return this.#transcriptBlockFinalized;
|
|
193
255
|
}
|
|
194
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Whether this still-live block's scrolled-off rows may be committed to
|
|
259
|
+
* immutable native scrollback (the {@link TranscriptContainer} durable-
|
|
260
|
+
* snapshot path). Reflowing Markdown — a streaming mermaid diagram or a GFM
|
|
261
|
+
* table — re-lays-out its body as source arrives (the diagram reshapes, the
|
|
262
|
+
* table re-aligns its columns), so committing an intermediate layout strands
|
|
263
|
+
* a stale fragment in native scrollback that only a full repaint (Ctrl+L) can
|
|
264
|
+
* clear. While such content is still streaming the block therefore stays
|
|
265
|
+
* wholly in the repaintable live region and commits once, at its final
|
|
266
|
+
* layout, when the turn finalizes.
|
|
267
|
+
*/
|
|
268
|
+
isTranscriptBlockCommitStable(): boolean {
|
|
269
|
+
return this.#transcriptBlockFinalized || !this.#hasLiveReflowingMarkdown;
|
|
270
|
+
}
|
|
271
|
+
|
|
195
272
|
getTranscriptBlockVersion(): number {
|
|
196
273
|
return this.#blockVersion;
|
|
197
274
|
}
|
|
@@ -418,6 +495,15 @@ export class AssistantMessageComponent extends Container {
|
|
|
418
495
|
this.#lastMessage = message;
|
|
419
496
|
this.#lastUpdateTransient = opts?.transient === true;
|
|
420
497
|
|
|
498
|
+
// Streaming reflowing Markdown (a mermaid diagram reshaping, a GFM table
|
|
499
|
+
// re-aligning columns) re-lays-out its body each frame; see
|
|
500
|
+
// isTranscriptBlockCommitStable. Detect it from raw text — a Markdown
|
|
501
|
+
// parser only resolves these once the closing fence / delimiter row
|
|
502
|
+
// arrives, but the stale native-scrollback commits happen mid-stream.
|
|
503
|
+
this.#hasLiveReflowingMarkdown = message.content.some(
|
|
504
|
+
content => content.type === "text" && detectLiveReflowingMarkdown(content.text),
|
|
505
|
+
);
|
|
506
|
+
|
|
421
507
|
// Fast path: reuse Markdown children when shape is stable during streaming
|
|
422
508
|
if (this.#tryFastPathUpdate(message)) return;
|
|
423
509
|
|
|
@@ -20,4 +20,5 @@ Press ctrl+r to search your prompt history and reuse a past message
|
|
|
20
20
|
Pair up live: `/collab` shares your session through an end-to-end encrypted relay link — a teammate runs `/join <link>` to watch tool calls stream and prompt the agent from their own omp
|
|
21
21
|
Press ← ← to drill into a running or finished agent and inspect its tool calls and transcript
|
|
22
22
|
Hit a Codex rate limit? `/usage reset` spends a saved reset credit to immediately restore your quota
|
|
23
|
-
No native tool_calling? Inference provider botches parsing them? `PI_DIALECT=glm|kimi|anthropic…` rolls it locally for them!
|
|
23
|
+
No native tool_calling? Inference provider botches parsing them? `PI_DIALECT=glm|kimi|anthropic…` rolls it locally for them!
|
|
24
|
+
Turn on `/advisor` to attach a second model that reviews every turn and quietly injects advice [NEW]
|
|
@@ -29,16 +29,73 @@ export const WELCOME_SESSION_SLOTS = 4;
|
|
|
29
29
|
*/
|
|
30
30
|
export const WELCOME_LSP_SLOTS = 4;
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
/** Trailing marker that flags a tip as a "what's new" callout. Stripped before
|
|
33
|
+
* wrapping (with any preceding whitespace) and replaced by {@link NEW_TAG_TEXT}
|
|
34
|
+
* painted as a shimmering rainbow. Non-global so `.test` stays stateless. */
|
|
35
|
+
const NEW_TIP_MARKER = /\s*\[NEW\]\s*$/;
|
|
36
|
+
|
|
37
|
+
/** Visible text rendered in place of {@link NEW_TIP_MARKER}. */
|
|
38
|
+
const NEW_TAG_TEXT = "NEW!";
|
|
39
|
+
|
|
40
|
+
/** Milliseconds for one full hue rotation of the rainbow "NEW!" tag. */
|
|
41
|
+
const NEW_GLOW_PERIOD_MS = 1500;
|
|
42
|
+
|
|
43
|
+
/** Selection weight for "[NEW]" tips; ordinary tips weigh 1, so a freshly added
|
|
44
|
+
* affordance surfaces this many times as often. */
|
|
45
|
+
const NEW_TIP_WEIGHT = 4;
|
|
46
|
+
|
|
47
|
+
/** Per-tip selection weights, parallel to {@link TIPS}. */
|
|
48
|
+
const TIP_WEIGHTS: readonly number[] = TIPS.map(tip => (NEW_TIP_MARKER.test(tip) ? NEW_TIP_WEIGHT : 1));
|
|
49
|
+
const TIP_WEIGHT_TOTAL = TIP_WEIGHTS.reduce((sum, weight) => sum + weight, 0);
|
|
50
|
+
|
|
51
|
+
/** Pick a tip at random, biased toward "[NEW]" tips by {@link NEW_TIP_WEIGHT}.
|
|
52
|
+
* Returns "" when no tips are embedded. */
|
|
53
|
+
function pickWeightedTip(): string {
|
|
54
|
+
if (TIPS.length === 0) return "";
|
|
55
|
+
let r = Math.random() * TIP_WEIGHT_TOTAL;
|
|
56
|
+
for (let i = 0; i < TIPS.length; i++) {
|
|
57
|
+
r -= TIP_WEIGHTS[i] ?? 1;
|
|
58
|
+
if (r < 0) return TIPS[i] ?? "";
|
|
59
|
+
}
|
|
60
|
+
return TIPS[TIPS.length - 1] ?? "";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type ColorEncoding = "ansi-16m" | "ansi-256";
|
|
64
|
+
|
|
65
|
+
/** Paint each glyph of {@link NEW_TAG_TEXT} on a moving HSL rainbow. `phase`
|
|
66
|
+
* rotates the hue offset cyclically; successive renders with increasing phase
|
|
67
|
+
* shimmer, while a fixed phase yields a still rainbow. */
|
|
68
|
+
function renderNewTag(phase: number, encoding: ColorEncoding): string {
|
|
69
|
+
const bold = "\x1b[1m";
|
|
70
|
+
const reset = "\x1b[0m";
|
|
71
|
+
const wrapped = ((phase % 1) + 1) % 1;
|
|
72
|
+
const chars = [...NEW_TAG_TEXT];
|
|
73
|
+
let out = bold;
|
|
74
|
+
let prev = "";
|
|
75
|
+
for (let i = 0; i < chars.length; i++) {
|
|
76
|
+
const hue = Math.round(((i / chars.length + wrapped) % 1) * 360);
|
|
77
|
+
const color = Bun.color(`hsl(${hue}, 95%, 60%)`, encoding) ?? "";
|
|
78
|
+
if (color !== prev) {
|
|
79
|
+
out += color;
|
|
80
|
+
prev = color;
|
|
81
|
+
}
|
|
82
|
+
out += chars[i];
|
|
83
|
+
}
|
|
84
|
+
return out + reset;
|
|
85
|
+
}
|
|
86
|
+
export function renderWelcomeTip(tip: string, boxWidth: number, phase = 0): string[] {
|
|
33
87
|
const label = "Tip: ";
|
|
34
88
|
const labelWidth = visibleWidth(label);
|
|
35
89
|
const bodyBudget = boxWidth - 1 - labelWidth; // 1 = leading indent
|
|
36
90
|
if (bodyBudget < 8) return [];
|
|
37
91
|
|
|
38
|
-
const
|
|
92
|
+
const isNew = NEW_TIP_MARKER.test(tip);
|
|
93
|
+
const body = isNew ? tip.replace(NEW_TIP_MARKER, "") : tip;
|
|
94
|
+
|
|
95
|
+
const wrappedBody = wrapTextWithAnsi(replaceTabs(body), bodyBudget);
|
|
39
96
|
if (wrappedBody.length === 0) return [];
|
|
40
97
|
|
|
41
|
-
const encoding = TERMINAL.trueColor ? "ansi-16m" : "ansi-256";
|
|
98
|
+
const encoding: ColorEncoding = TERMINAL.trueColor ? "ansi-16m" : "ansi-256";
|
|
42
99
|
const purple = Bun.color("#b48cff", encoding) ?? "";
|
|
43
100
|
const lightBlue = Bun.color("#9ccfff", encoding) ?? "";
|
|
44
101
|
const italic = "\x1b[3m";
|
|
@@ -46,11 +103,27 @@ export function renderWelcomeTip(tip: string, boxWidth: number): string[] {
|
|
|
46
103
|
const reset = "\x1b[0m";
|
|
47
104
|
const continuationIndent = padding(labelWidth);
|
|
48
105
|
|
|
49
|
-
|
|
106
|
+
const lines = wrappedBody.map((line, index) =>
|
|
50
107
|
index === 0
|
|
51
|
-
? ` ${italic}${purple}${label}${dim}${lightBlue}${
|
|
52
|
-
: ` ${italic}${continuationIndent}${dim}${lightBlue}${
|
|
108
|
+
? ` ${italic}${purple}${label}${dim}${lightBlue}${line}${reset}`
|
|
109
|
+
: ` ${italic}${continuationIndent}${dim}${lightBlue}${line}${reset}`,
|
|
53
110
|
);
|
|
111
|
+
|
|
112
|
+
if (isNew) {
|
|
113
|
+
// Append the rainbow tag to the final body line when it fits within the
|
|
114
|
+
// box; otherwise drop it onto its own indented continuation line so the
|
|
115
|
+
// styled glyphs never overflow or reflow the wrapped body.
|
|
116
|
+
const tag = renderNewTag(phase, encoding);
|
|
117
|
+
const tagWidth = 1 + visibleWidth(NEW_TAG_TEXT); // 1 = space separator
|
|
118
|
+
const lastLine = lines[lines.length - 1];
|
|
119
|
+
if (lastLine !== undefined && visibleWidth(lastLine) + tagWidth <= boxWidth) {
|
|
120
|
+
lines[lines.length - 1] = `${lastLine} ${tag}`;
|
|
121
|
+
} else {
|
|
122
|
+
lines.push(` ${continuationIndent}${tag}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return lines;
|
|
54
127
|
}
|
|
55
128
|
|
|
56
129
|
export interface RecentSession {
|
|
@@ -89,7 +162,7 @@ export class WelcomeComponent implements Component {
|
|
|
89
162
|
if (theme.getSymbolPreset() === "unicode" && Math.random() < 0.1) {
|
|
90
163
|
this.#selectedTip = "Please use nerdfont 😭.";
|
|
91
164
|
} else {
|
|
92
|
-
this.#selectedTip =
|
|
165
|
+
this.#selectedTip = pickWeightedTip();
|
|
93
166
|
}
|
|
94
167
|
}
|
|
95
168
|
return this.#selectedTip || undefined;
|
|
@@ -327,7 +400,12 @@ export class WelcomeComponent implements Component {
|
|
|
327
400
|
#renderTip(boxWidth: number): string[] {
|
|
328
401
|
const tip = this.tip;
|
|
329
402
|
if (!tip) return [];
|
|
330
|
-
|
|
403
|
+
// A trailing "[NEW]" marker paints an animated rainbow "NEW!" tag. Derive
|
|
404
|
+
// its hue phase from wall-clock time so it shimmers across the welcome
|
|
405
|
+
// intro's re-render frames, then settles into a still rainbow once the box
|
|
406
|
+
// caches its resting frame. Non-"[NEW]" tips ignore the phase entirely.
|
|
407
|
+
const phase = NEW_TIP_MARKER.test(tip) ? performance.now() / NEW_GLOW_PERIOD_MS : 0;
|
|
408
|
+
return renderWelcomeTip(tip, boxWidth, phase);
|
|
331
409
|
}
|
|
332
410
|
|
|
333
411
|
/** Center text within a given width */
|
|
@@ -186,7 +186,7 @@ export class EventController {
|
|
|
186
186
|
}
|
|
187
187
|
#updateWorkingMessageFromIntent(intent: unknown): void {
|
|
188
188
|
if (this.ctx.session.isAborting) return;
|
|
189
|
-
// Streamed JSON can deliver non-string `
|
|
189
|
+
// Streamed JSON can deliver non-string `i` (object, number, boolean) before
|
|
190
190
|
// schema validation; `?.` only guards null/undefined, so guard the type too.
|
|
191
191
|
if (typeof intent !== "string") return;
|
|
192
192
|
const trimmed = intent.trim();
|
|
@@ -1,26 +1,18 @@
|
|
|
1
1
|
You are a terse, evidence-first engineer: every sentence carries a fact, a decision, or a risk.
|
|
2
2
|
|
|
3
3
|
# Tone
|
|
4
|
-
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
- Compress reasoning into facts, constraints, tradeoffs, decisions, and checks. Action-oriented and dense.
|
|
10
|
-
- Do not hide uncertainty: state it briefly at the specific claim, name the tradeoff, and pick the boring/safe option.
|
|
4
|
+
- Terse fragments when clearer. Skip ceremony, hedging, summaries, filler, and marketing language.
|
|
5
|
+
- Don't narrate obvious steps or over-explain basics. Assume a technical reader.
|
|
6
|
+
- Be concrete: exact files, symbols, APIs, state fields, edge cases, verification.
|
|
7
|
+
- Compress reasoning into facts, constraints, tradeoffs, decisions, checks. Lead with the conclusion, then evidence.
|
|
8
|
+
- Don't hide uncertainty: state it at the specific claim, name the tradeoff, pick the boring/safe option.
|
|
11
9
|
- For code, focus on invariants, risks, and verification.
|
|
12
|
-
- Lead with the conclusion, then concrete evidence: changed files and verification.
|
|
13
10
|
|
|
14
11
|
# Reasoning Format
|
|
15
|
-
- Problem: what
|
|
16
|
-
- Decision: what to do & why (concrete facts).
|
|
17
|
-
- Check: what can break & how to verify result.
|
|
18
|
-
- Next: the next concrete edit/action.
|
|
12
|
+
- Problem: what's wrong. Decision: what to do & why. Check: what can break & how to verify. Next: the next concrete action.
|
|
19
13
|
|
|
20
14
|
# Succinct Patterns
|
|
21
|
-
- Y →
|
|
22
|
-
- This is safe: Z.
|
|
23
|
-
- Could do A, but B avoids C.
|
|
15
|
+
- Y → need update X. This is safe: Z. Could do A, but B avoids C.
|
|
24
16
|
|
|
25
17
|
# Escalation
|
|
26
|
-
Push back when the plan hides risk or a claim is wrong: name the risk, show
|
|
18
|
+
Push back when the plan hides risk or a claim is wrong: name the risk, show evidence, propose the alternative. Once overruled, execute the user's call without relitigating.
|