227 lines
12 KiB
HTML
227 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Saturday, 2 August 2026 — Roject</title>
|
|
<link rel="stylesheet" href="../../../../_assets_/styles.css">
|
|
<link rel="stylesheet" href="../../../../_assets_/nav.css">
|
|
</head>
|
|
<body>
|
|
<div class="page">
|
|
|
|
<header>
|
|
<p class="date">Saturday, 2 August 2026</p>
|
|
<h1>Session History</h1>
|
|
<p class="subtitle">Rojo bug fixes, Claude API and Claude Code providers, auth session refresh overhaul (7 phases), library-ts ESM import fix.</p>
|
|
</header>
|
|
|
|
<section>
|
|
<h2>What we built</h2>
|
|
|
|
<div class="card">
|
|
<h3>Rojo local-mode bug fixes (three)</h3>
|
|
<p>
|
|
All three bugs shared the same root cause: <code>rojo-chat-panel</code> only checked
|
|
<code>editor.projectId</code>, silently ignoring <code>localRoot</code> and
|
|
<code>remoteProject</code> modes.
|
|
</p>
|
|
<ul style="margin-top:0.5rem;line-height:1.9">
|
|
<li><strong>Detection</strong> — <code>_loadRojos()</code> now uses a three-way branch
|
|
matching <code>Editor.ts</code>'s three mode fields, hitting
|
|
<code>/api/local/rojos?root=…</code> for local projects.</li>
|
|
<li><strong>Creation</strong> — <code>_createRojo()</code> received the same fix,
|
|
posting to <code>/api/local/rojos/create</code> with <code>{ root, parentDir }</code>.</li>
|
|
<li><strong>System prompt</strong> — <code>_send()</code> was sending
|
|
<code>body.projectId = ''</code> (falsy), so the server skipped reading the rojo file.
|
|
Fixed by sending <code>localRoot</code> in the body and branching server-side
|
|
with a path-containment security check.</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Claude API provider (<code>provider: "claude"</code>)</h3>
|
|
<p>
|
|
Installed <code>@anthropic-ai/sdk</code> and added a second LLM path to
|
|
<code>RojosAgent.ts</code>:
|
|
</p>
|
|
<ul style="margin-top:0.5rem;line-height:1.9">
|
|
<li><code>AgentConfig</code> extended with optional <code>provider?: "openai" | "claude" | "claude-code"</code>
|
|
and <code>baseURL</code> made optional.</li>
|
|
<li>Separate <code>claudeSessions</code> map (<code>Anthropic.MessageParam[]</code>)
|
|
keeps conversation history independently of the LangChain OpenAI sessions.</li>
|
|
<li>Streaming via <code>client.messages.stream()</code>; text deltas yielded in
|
|
real-time. After iteration, <code>stream.finalMessage()</code> is used to collect
|
|
tool use blocks for the agentic loop.</li>
|
|
<li>OpenAI-format tools converted to Claude format by <code>toClaudeTool()</code>
|
|
(wraps <code>parameters</code> as <code>input_schema</code>).</li>
|
|
<li>Route <code>rojos.ts</code>: <code>ep.type === "claude"</code> branch sets
|
|
<code>config.provider</code>, defaults model to <code>claude-opus-4-8</code>.</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Claude Code subprocess provider (<code>provider: "claude-code"</code>)</h3>
|
|
<p>
|
|
Adds a third provider path that spawns the local <code>claude.exe</code> binary
|
|
(already installed via <code>@anthropic-ai/claude-code</code>) as a subprocess.
|
|
No API key needed — reuses the existing Claude Code login from the system keychain.
|
|
</p>
|
|
<ul style="margin-top:0.5rem;line-height:1.9">
|
|
<li>CLI flags: <code>--print --output-format stream-json --verbose
|
|
--dangerously-skip-permissions</code>.</li>
|
|
<li>First message uses <code>--session-id <conversationId></code> to create a
|
|
persistent session; subsequent messages use <code>--resume <conversationId></code>
|
|
to continue it — full multi-turn conversation.</li>
|
|
<li>System prompt passed via <code>--append-system-prompt</code> to preserve Claude
|
|
Code's own built-in instructions.</li>
|
|
<li><code>spawnJsonLines()</code> is an async generator backed by an event queue;
|
|
stdout is parsed line-by-line as newline-delimited JSON.
|
|
<code>type === "assistant"</code> events are converted to <code>AgentEvent</code>.</li>
|
|
<li>Binary located at runtime via <code>findClaudeBin()</code> (checks
|
|
<code>node_modules/@anthropic-ai/claude-code/bin/claude.exe</code> first,
|
|
falls back to <code>.bin/claude.cmd</code>).</li>
|
|
</ul>
|
|
<p style="margin-top:0.75rem">
|
|
Three bugs fixed during initial testing:
|
|
</p>
|
|
<ul style="margin-top:0.5rem;line-height:1.9">
|
|
<li><strong>Missing <code>--verbose</code></strong> — required by the CLI when
|
|
<code>stream-json</code> output format is used; without it the process exits with
|
|
an error immediately.</li>
|
|
<li><strong>Stdin not closed</strong> — process waited 3 s for piped input before
|
|
starting. Fixed by <code>stdio: ["ignore", "pipe", "pipe"]</code>.</li>
|
|
<li><strong><code>shell: true</code> on Windows</strong> — cmd.exe mangled the user
|
|
message (split on spaces, <code>?</code> treated as wildcard), so Claude received
|
|
only a fragment. Fixed by <code>shell: false</code> with the direct <code>.exe</code>
|
|
path, letting Node.js call <code>CreateProcess()</code> directly.</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Rojo settings panel: <code>claude</code> and <code>claude-code</code> endpoint types</h3>
|
|
<p>
|
|
Two new options added to the endpoint type selector:
|
|
</p>
|
|
<ul style="margin-top:0.5rem;line-height:1.9">
|
|
<li><strong>Claude API (Anthropic)</strong> — shows the API key field; hides the Base URL
|
|
row (not needed). Model field stays visible with a <code><datalist></code>
|
|
suggesting <code>claude-opus-4-8</code>, <code>claude-sonnet-4-6</code>,
|
|
<code>claude-haiku-4-5</code>.</li>
|
|
<li><strong>Claude Code (local, no key)</strong> — hides the external section and model
|
|
row entirely; shows a brief info note explaining that the local Claude Code login
|
|
is used.</li>
|
|
</ul>
|
|
<p style="margin-top:0.75rem">
|
|
<code>_updateEndpointVisibility()</code> controls all show/hide logic including the
|
|
<code>.rsp-url-row</code> sub-element inside the external section.
|
|
</p>
|
|
</div>
|
|
|
|
</section>
|
|
|
|
<div class="card">
|
|
<h3>Auth session refresh overhaul — 7 phases</h3>
|
|
<p>
|
|
Full design doc: <code>workspace/history/2026/08-August/02-Saturday/auth-update.page</code>.
|
|
Root cause: two independent bugs — browser tabs racing a single-use refresh token,
|
|
and Electron never refreshing after its one-shot startup call.
|
|
</p>
|
|
<ul style="margin-top:0.5rem;line-height:1.9">
|
|
<li><strong>Phase 1</strong> — rokojori-auth grace window: <code>refreshTokens</code>
|
|
soft-deleted with <code>usedAt</code>/<code>replacedBy</code>; concurrent reuse
|
|
within 10 s resolves to the same replacement pair.</li>
|
|
<li><strong>Phase 2</strong> — <code>TokenUpdater</code>: central state machine
|
|
(<code>valid|refreshing|expired|network-error</code>) with 5-min timer +
|
|
<code>ActivityAnalyser.onActive</code>. <code>jwtMiddleware</code> proactively
|
|
rotates cookie server-side within 15 min of expiry.</li>
|
|
<li><strong>Phase 3</strong> — <code>GuardedCall</code>: singleton with three tiers
|
|
(user / editor / silent), pre-flight state check, retry with backoff.</li>
|
|
<li><strong>Phase 4</strong> — Web Locks leader election across browser tabs;
|
|
<code>BroadcastChannel</code> state sharing to followers.</li>
|
|
<li><strong>Phase 5</strong> — Electron token updater in <code>main.ts</code>:
|
|
server clock offset via <code>Date</code> header, JWT <code>exp</code> decoded
|
|
directly, 5-min periodic proactive refresh.</li>
|
|
<li><strong>Phase 6</strong> — <code>POST /api/auth/new-session</code> in
|
|
rokojori-auth: mints a fresh independent token pair for an authenticated user.</li>
|
|
<li><strong>Phase 7</strong> — Electron heartbeat: running instance writes
|
|
<code>{ accessToken, timestamp }</code> every 10 s; new instance skips login
|
|
if heartbeat ≤ 30 s old by calling <code>/api/auth/new-session</code>.</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>library-ts: .js extensions added to all browser relative imports</h3>
|
|
<p>
|
|
Roject's client is served as unbundled browser-native ESM — no bundler, Express
|
|
static serves individual <code>.js</code> files. All relative imports in
|
|
<code>source/library-ts/browser/</code> were extension-less, causing cascading
|
|
404s whenever any previously-unused module entered the import chain (first triggered
|
|
by <code>TokenUpdater</code> importing <code>ActivityAnalyser</code>).
|
|
</p>
|
|
<p style="margin-top:0.75rem">
|
|
A one-shot Node.js script (<code>fix-library-imports.js</code>) added <code>.js</code>
|
|
to all relative imports across 101 files in a single pass. TypeScript with
|
|
<code>moduleResolution: "bundler"</code> accepts <code>.js</code> extensions in
|
|
source even for <code>.ts</code> files — clean build confirmed.
|
|
</p>
|
|
</div>
|
|
|
|
</section>
|
|
|
|
<section>
|
|
<h2>Key decisions</h2>
|
|
|
|
<div class="card">
|
|
<p>
|
|
<strong>Separate session stores per provider.</strong>
|
|
The OpenAI path keeps <code>BaseMessage[]</code> (LangChain); the Claude API path
|
|
keeps <code>Anthropic.MessageParam[]</code>; the Claude Code path keeps only a
|
|
<code>Set<string></code> of initialised session IDs (history lives in the
|
|
claude subprocess's own storage). Mixing them would require format translation on
|
|
every turn.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<p>
|
|
<strong><code>--append-system-prompt</code> instead of <code>--system-prompt</code>.</strong>
|
|
<code>--system-prompt</code> replaces Claude Code's entire built-in prompt, which
|
|
would break the subprocess's tool-use instructions. <code>--append-system-prompt</code>
|
|
adds the rojo's persona on top without disturbing the base behaviour.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<p>
|
|
<strong><code>shell: false</code> with direct <code>.exe</code> path on Windows.</strong>
|
|
Using <code>shell: true</code> routes through <code>cmd.exe</code>, which interprets
|
|
spaces as argument separators and <code>?</code> as a wildcard — making multi-word
|
|
user messages arrive garbled. Direct process creation passes each argv element as a
|
|
properly quoted string.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<p>
|
|
<strong>Claude Code as a no-key Claude backend.</strong>
|
|
Because the subprocess inherits the OS user's existing Claude Code login (keychain /
|
|
<code>~/.claude/</code>), no Anthropic API key is required in the rojo settings.
|
|
The Claude Code subprocess can also use its own full tool set (Read, Bash, Grep, etc.),
|
|
making it more capable than the pure API path for code-aware tasks.
|
|
</p>
|
|
</div>
|
|
|
|
</section>
|
|
|
|
<footer>
|
|
Roject — session history
|
|
</footer>
|
|
|
|
</div>
|
|
<script>var NAV_ROOT = '../../../../';</script>
|
|
<script src="../../../../_assets_/nav-data.js"></script>
|
|
<script src="../../../../_assets_/nav.js"></script>
|
|
</body>
|
|
</html>
|