chore: version packages

This commit is contained in:
arkon
2026-03-28 16:49:55 +01:00
parent 692c894760
commit a84ef52992
10 changed files with 226 additions and 54 deletions
+6
View File
@@ -1,5 +1,11 @@
# aicodeman
## 0.5.5
### Patch Changes
- Add 1M Opus context quick setting — per-case and global toggle that writes `model: "opus[1m]"` to `.claude/settings.local.json` when creating new sessions. Fix mobile layout: banners (respawn, timer, orchestrator) between header and main content now visible by switching from margin-top on `.main` to padding-top on `.app`. Add tablet-optimized respawn banner styles and mobile phone banner refinements.
## 0.5.4
### Patch Changes
+1 -1
View File
@@ -52,7 +52,7 @@ When user says "COM":
4. **Sync CLAUDE.md version**: Update the `**Version**` line below to match the new version from `package.json`
5. **Commit and deploy**: `git add -A && git commit -m "chore: version packages" && git push && npm run build && systemctl --user restart codeman-web`
**Version**: 0.5.4 (must match `package.json`)
**Version**: 0.5.5 (must match `package.json`)
## Project Overview
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "aicodeman",
"version": "0.5.4",
"version": "0.5.5",
"description": "The missing control plane for AI coding agents - run 20 autonomous agents with real-time monitoring and session persistence",
"type": "module",
"main": "dist/index.js",
+28
View File
@@ -116,6 +116,34 @@ export async function updateCaseEnvVars(casePath: string, envVars: Record<string
await writeFile(settingsPath, JSON.stringify(existing, null, 2) + '\n');
}
/**
* Updates the `model` field in .claude/settings.local.json for the given case path.
* Pass a non-empty string to set, or empty/null to remove.
*/
export async function updateCaseModel(casePath: string, model: string | null): Promise<void> {
const claudeDir = join(casePath, '.claude');
if (!existsSync(claudeDir)) {
await mkdir(claudeDir, { recursive: true });
}
const settingsPath = join(claudeDir, 'settings.local.json');
let existing: Record<string, unknown> = {};
try {
existing = JSON.parse(await readFile(settingsPath, 'utf-8'));
} catch {
existing = {};
}
if (model) {
existing.model = model;
} else {
delete existing.model;
}
await writeFile(settingsPath, JSON.stringify(existing, null, 2) + '\n');
}
/**
* Writes hooks config to .claude/settings.local.json in the given case path.
* Merges with existing file content, only touching the `hooks` key.
+18
View File
@@ -384,6 +384,11 @@
<span>Agent Teams</span>
</label>
<span class="form-hint">Enable experimental Agent Teams for new sessions in this case</span>
<label class="checkbox-inline" style="margin-top: 6px;">
<input type="checkbox" id="caseOpusContext1m" onchange="app.onCaseSettingChanged()">
<span>1M Opus Context</span>
</label>
<span class="form-hint">Use 1M token context window for new sessions</span>
</div>
</div>
<!-- Mobile-only case button + gear -->
@@ -408,6 +413,11 @@
<span>Agent Teams</span>
</label>
<span class="form-hint">Enable Agent Teams for new sessions</span>
<label class="checkbox-inline" style="margin-top: 6px;">
<input type="checkbox" id="caseOpusContext1mMobile" onchange="app.onCaseSettingChangedMobile()">
<span>1M Opus Context</span>
</label>
<span class="form-hint">Use 1M token context window</span>
</div>
</div>
@@ -1046,6 +1056,14 @@
</label>
<span class="form-hint">Enable experimental Agent Teams for all new Claude sessions (disabled by default)</span>
</div>
<div class="form-row form-row-switch">
<label>1M Opus Context</label>
<label class="switch">
<input type="checkbox" id="appSettingsOpusContext1m">
<span class="slider"></span>
</label>
<span class="form-hint">Use 1M token context window (model: opus[1m]) for all new sessions</span>
</div>
<!-- Nice Priority Section -->
<div class="form-section-header">Nice Priority</div>
<div class="form-row form-row-switch">
+144 -47
View File
@@ -65,13 +65,14 @@ html.mobile-init .file-browser-panel {
max-height: calc(48px + var(--safe-area-top));
}
/* Add top margin to main content to account for fixed header */
.main {
margin-top: 54px;
/* Push ALL content below fixed header (not just .main) so banners
(respawn, timer, orchestrator) between header and main are visible */
.app {
padding-top: 54px;
}
.ios-device .main {
margin-top: calc(54px + var(--safe-area-top));
.ios-device .app {
padding-top: calc(54px + var(--safe-area-top));
}
/* Font controls - smaller on tablet, visibility controlled by JS */
@@ -235,6 +236,37 @@ html.mobile-init .file-browser-panel {
font-size: 0.7rem;
}
/* ---- Respawn Banner: Tablet Optimizations ---- */
.respawn-banner {
padding: 0.25rem 0.5rem;
padding-left: calc(0.5rem + var(--safe-area-left));
padding-right: calc(0.5rem + var(--safe-area-right));
font-size: 0.65rem;
}
.respawn-compact-layout {
gap: 0.75rem;
}
.respawn-status-row1 {
gap: 0.4rem;
}
.respawn-action-log {
max-height: 2.6em;
font-size: 0.6rem;
}
.respawn-countdown-timer {
font-size: 0.55rem;
}
/* Respawn timer bar — slimmer on tablet */
.respawn-timer-bar {
width: 24px;
}
/* Show desktop voice button on tablet (hidden by max-width:1023px in styles.css,
mobile .btn-voice-mobile only shows at <430px) */
.toolbar-center .btn-toolbar.btn-voice {
@@ -327,18 +359,22 @@ html.mobile-init .file-browser-panel {
max-height: calc(36px + var(--safe-area-top));
}
/* Add top margin to main content to account for fixed header,
and bottom padding for the fixed toolbar (40px) so terminal
content doesn't extend behind it. JS overrides paddingBottom
/* Push ALL content below fixed header (not just .main) so banners
(respawn, timer, orchestrator) between header and main are visible.
Bottom padding for the fixed toolbar (40px) so terminal content
doesn't extend behind it. JS overrides paddingBottom on .main
when keyboard is visible, and resetLayout() clears the inline
style to re-expose this CSS value. */
.main {
margin-top: 42px;
padding-bottom: calc(40px + var(--safe-area-bottom));
.app {
padding-top: 42px;
}
.ios-device .main {
margin-top: calc(42px + var(--safe-area-top));
.ios-device .app {
padding-top: calc(42px + var(--safe-area-top));
}
.main {
padding-bottom: calc(40px + var(--safe-area-bottom));
}
/* iOS Safari: toolbar is pushed up by (100vh - --app-height) to clear the
@@ -1213,31 +1249,70 @@ html.mobile-init .file-browser-panel {
/* ---- Respawn Banner: Mobile Optimizations ---- */
.respawn-banner {
padding: 0.4rem 0.5rem;
padding: 0.3rem 0.5rem;
padding-left: calc(0.5rem + var(--safe-area-left));
padding-right: calc(0.5rem + var(--safe-area-right));
font-size: 0.7rem;
font-size: 0.65rem;
}
/* Stack banner vertically on mobile — status on top, action log below */
.respawn-compact-layout {
flex-direction: column;
gap: 0.25rem;
gap: 0.15rem;
}
/* Let status column shrink to fit */
.respawn-status-col {
flex-shrink: 1;
min-width: 0;
gap: 0.1rem;
}
/* Wrap status row items — tokens/timer may go to second line */
/* Row1: wrap allowed, key info prioritized via order */
.respawn-status-row1 {
flex-wrap: wrap;
gap: 0.35rem;
gap: 0.25rem;
row-gap: 0.1rem;
}
/* Stop button — 44px touch target */
/* State label — smaller on phone, stays first */
.respawn-state {
font-size: 0.6rem;
padding: 0.05rem 0.3rem;
}
/* Confidence badge — compact */
.respawn-banner .detection-confidence {
font-size: 0.5rem;
padding: 0.02rem 0.2rem;
}
/* Cycles — compact */
.respawn-cycles {
font-size: 0.55rem;
}
/* Run timer — compact */
.respawn-timer {
font-size: 0.55rem;
padding: 0 0.25rem;
}
/* Tokens — compact, truncate if long */
.respawn-tokens {
font-size: 0.55rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 120px;
}
/* Spinner icon — smaller on phone */
.respawn-indicator {
font-size: 0.65rem;
}
/* Stop button — 36px touch target, pushed right */
.respawn-banner .btn-icon-only {
min-width: 36px;
min-height: 36px;
@@ -1247,26 +1322,50 @@ html.mobile-init .file-browser-panel {
justify-content: center;
margin-left: auto;
border-radius: 6px;
flex-shrink: 0;
}
/* Action log — horizontal strip below status, border on top instead of left */
.respawn-action-log {
border-left: none;
border-top: 1px solid rgba(34, 197, 94, 0.2);
padding-left: 0;
padding-top: 0.25rem;
max-height: 2em;
font-size: 0.6rem;
/* Row2: tighter spacing, smaller text */
.respawn-status-row2 {
font-size: 0.55rem;
gap: 0.3rem;
min-height: 0;
flex-wrap: wrap;
}
/* Countdown timers — tighter on mobile */
/* Detection badges — smaller on phone */
.respawn-banner .detection-hook,
.respawn-banner .detection-ai-check,
.respawn-banner .detection-status {
font-size: 0.55rem;
padding: 0.02rem 0.2rem;
}
/* Countdown timers — inline, no progress bars */
.respawn-countdown-timers {
gap: 0.2rem;
}
.respawn-countdown-timer {
font-size: 0.5rem;
padding: 0.02rem 0.2rem;
gap: 0.15rem;
}
/* Hide progress bars on phone — values are enough */
.respawn-countdown-timer .respawn-timer-bar {
display: none;
}
/* Action log — single-line strip below status, show latest entry only */
.respawn-action-log {
border-left: none;
border-top: 1px solid rgba(34, 197, 94, 0.15);
padding-left: 0;
padding-top: 0.15rem;
max-height: 1.4em;
font-size: 0.55rem;
padding: 0.05rem 0.25rem;
overflow: hidden;
}
/* ---- Session Options Modal: Respawn Tab Mobile Optimizations ---- */
@@ -1292,28 +1391,27 @@ html.mobile-init .file-browser-panel {
border-radius: 5px;
}
/* Duration preset buttons — single row, all 7 items */
/* Duration preset buttons — grid layout, 4 columns for even spacing */
.duration-presets {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.2rem;
}
.duration-preset-btn {
min-height: 28px;
padding: 0.2rem 0.4rem;
min-height: 32px;
padding: 0.2rem 0.25rem;
font-size: 0.65rem;
border-radius: 5px;
text-align: center;
flex: 1 1 auto;
min-width: 0;
}
/* Custom duration — inline with other presets */
/* Custom duration — spans full row below */
.duration-custom {
flex-wrap: wrap;
grid-column: 1 / -1;
display: flex;
gap: 0.25rem;
flex: 1 1 100%;
align-items: center;
}
.duration-custom .duration-preset-btn {
@@ -1331,24 +1429,23 @@ html.mobile-init .file-browser-panel {
font-size: 16px; /* Prevents iOS zoom */
}
/* Preset selector row — compact inline */
/* Preset selector row — full-width dropdown, buttons below */
.preset-selector {
display: flex;
flex-wrap: wrap;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.25rem;
}
.preset-selector select {
width: 100%;
min-height: 28px;
grid-column: 1 / -1;
min-height: 32px;
font-size: 16px; /* Prevents iOS zoom */
border-radius: 5px;
padding: 0.2rem 0.4rem;
}
.preset-selector .btn {
flex: 1;
min-height: 28px;
min-height: 32px;
font-size: 0.65rem;
border-radius: 5px;
padding: 0.2rem 0.4rem;
+18 -4
View File
@@ -324,6 +324,8 @@ Object.assign(CodemanApp.prototype, {
envOverrides.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = '1';
}
const hasEnvOverrides = Object.keys(envOverrides).length > 0;
const useOpus1m = caseSettings.opusContext1m || globalSettings.opusContext1mEnabled;
const modelOverride = useOpus1m ? 'opus[1m]' : '';
// Step 1: Create all sessions in parallel
this.terminal.writeln(`\x1b[90m Creating ${tabCount} session(s)...\x1b[0m`);
@@ -331,7 +333,11 @@ Object.assign(CodemanApp.prototype, {
fetch('/api/sessions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ workingDir, name, ...(hasEnvOverrides ? { envOverrides } : {}) })
body: JSON.stringify({
workingDir, name,
...(hasEnvOverrides ? { envOverrides } : {}),
...(modelOverride !== undefined ? { modelOverride } : {}),
})
}).then(r => r.json())
);
const createResults = await Promise.all(createPromises);
@@ -913,6 +919,7 @@ Object.assign(CodemanApp.prototype, {
const caseName = document.getElementById('quickStartCase').value || 'testcase';
const settings = this.getCaseSettings(caseName);
document.getElementById('caseAgentTeams').checked = settings.agentTeams;
document.getElementById('caseOpusContext1m').checked = settings.opusContext1m;
popover.classList.remove('hidden');
// Close on outside click (one-shot listener)
@@ -934,7 +941,7 @@ Object.assign(CodemanApp.prototype, {
const stored = localStorage.getItem('caseSettings_' + caseName);
if (stored) return JSON.parse(stored);
} catch { /* ignore */ }
return { agentTeams: false };
return { agentTeams: false, opusContext1m: false };
},
saveCaseSettings(caseName, settings) {
@@ -945,10 +952,13 @@ Object.assign(CodemanApp.prototype, {
const caseName = document.getElementById('quickStartCase').value || 'testcase';
const settings = this.getCaseSettings(caseName);
settings.agentTeams = document.getElementById('caseAgentTeams').checked;
settings.opusContext1m = document.getElementById('caseOpusContext1m').checked;
this.saveCaseSettings(caseName, settings);
// Sync mobile checkbox
// Sync mobile checkboxes
const mobileCheckbox = document.getElementById('caseAgentTeamsMobile');
if (mobileCheckbox) mobileCheckbox.checked = settings.agentTeams;
const mobileOpusCheckbox = document.getElementById('caseOpusContext1mMobile');
if (mobileOpusCheckbox) mobileOpusCheckbox.checked = settings.opusContext1m;
},
toggleCaseSettingsMobile() {
@@ -957,6 +967,7 @@ Object.assign(CodemanApp.prototype, {
const caseName = document.getElementById('quickStartCase').value || 'testcase';
const settings = this.getCaseSettings(caseName);
document.getElementById('caseAgentTeamsMobile').checked = settings.agentTeams;
document.getElementById('caseOpusContext1mMobile').checked = settings.opusContext1m;
popover.classList.remove('hidden');
const closeHandler = (e) => {
@@ -975,10 +986,13 @@ Object.assign(CodemanApp.prototype, {
const caseName = document.getElementById('quickStartCase').value || 'testcase';
const settings = this.getCaseSettings(caseName);
settings.agentTeams = document.getElementById('caseAgentTeamsMobile').checked;
settings.opusContext1m = document.getElementById('caseOpusContext1mMobile').checked;
this.saveCaseSettings(caseName, settings);
// Sync desktop checkbox
// Sync desktop checkboxes
const desktopCheckbox = document.getElementById('caseAgentTeams');
if (desktopCheckbox) desktopCheckbox.checked = settings.agentTeams;
const desktopOpusCheckbox = document.getElementById('caseOpusContext1m');
if (desktopOpusCheckbox) desktopOpusCheckbox.checked = settings.opusContext1m;
},
// ═══════════════════════════════════════════════════════════════
+2
View File
@@ -332,6 +332,7 @@ Object.assign(CodemanApp.prototype, {
};
// Claude Permissions settings
document.getElementById('appSettingsAgentTeams').checked = settings.agentTeamsEnabled ?? false;
document.getElementById('appSettingsOpusContext1m').checked = settings.opusContext1mEnabled ?? false;
// CPU Priority settings
const niceSettings = settings.nice || {};
document.getElementById('appSettingsNiceEnabled').checked = niceSettings.enabled ?? false;
@@ -1130,6 +1131,7 @@ Object.assign(CodemanApp.prototype, {
allowedTools: document.getElementById('appSettingsAllowedTools').value.trim(),
// Claude Permissions settings
agentTeamsEnabled: document.getElementById('appSettingsAgentTeams').checked,
opusContext1mEnabled: document.getElementById('appSettingsOpusContext1m').checked,
// CPU Priority settings
nice: {
enabled: document.getElementById('appSettingsNiceEnabled').checked,
+6 -1
View File
@@ -42,7 +42,7 @@ import {
validatePathWithinBase,
} from '../route-helpers.js';
import { AUTH_COOKIE_NAME } from '../middleware/auth.js';
import { writeHooksConfig, updateCaseEnvVars } from '../../hooks-config.js';
import { writeHooksConfig, updateCaseEnvVars, updateCaseModel } from '../../hooks-config.js';
import { generateClaudeMd } from '../../templates/claude-md.js';
import { imageWatcher } from '../../image-watcher.js';
import { getLifecycleLog } from '../../session-lifecycle-log.js';
@@ -166,6 +166,11 @@ export function registerSessionRoutes(
await updateCaseEnvVars(workingDir, body.envOverrides);
}
// Write model override to .claude/settings.local.json if provided
if (body.modelOverride !== undefined) {
await updateCaseModel(workingDir, body.modelOverride || null);
}
// Check OpenCode availability if requested
if (body.mode === 'opencode') {
const { isOpenCodeAvailable } = await import('../../utils/opencode-cli-resolver.js');
+2
View File
@@ -124,6 +124,8 @@ export const CreateSessionSchema = z.object({
mode: z.enum(['claude', 'shell', 'opencode']).optional(),
name: z.string().max(100).optional(),
envOverrides: safeEnvOverridesSchema,
/** Model override to write to .claude/settings.local.json (e.g., "opus[1m]"). Empty string clears. */
modelOverride: z.string().max(50).optional(),
openCodeConfig: OpenCodeConfigSchema,
/** Resume a previous Claude conversation by its session ID (used for reboot recovery) */
resumeSessionId: z