rojects/workspace/history/2026/08-August/02-Saturday/index.html

227 lines
12 KiB
HTML
Raw Normal View History

<!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 &lt;conversationId&gt;</code> to create a
persistent session; subsequent messages use <code>--resume &lt;conversationId&gt;</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>&lt;datalist&gt;</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&lt;string&gt;</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 &mdash; session history
</footer>
</div>
<script>var NAV_ROOT = '../../../../';</script>
<script src="../../../../_assets_/nav-data.js"></script>
<script src="../../../../_assets_/nav.js"></script>
</body>
</html>