Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 19 additions & 19 deletions src/agent/session/daily-summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ export async function generateHeartbeatActivityLog(
`# Heartbeat Activity — ${dateStr}`,
'',
'## Summary',
`- ${grouped.okCount} HEARTBEAT_OK`,
`- ${grouped.actions.length}回 アクション実行`,
`- ${grouped.okCount}x HEARTBEAT_OK`,
`- ${grouped.actions.length}x actions executed`,
'',
'## Activity Log',
'',
Expand Down Expand Up @@ -255,19 +255,19 @@ export async function generateDailySummary(
// Sessions section
lines.push('## Sessions');
if (sessions.length === 0) {
lines.push('(セッションなし)', '');
lines.push('(No sessions)', '');
} else {
for (const s of sessions) {
lines.push(`- ${s.trigger}: ${s.title}${s.durationMin}, ${s.tokens}トークン)`);
lines.push(`- ${s.trigger}: ${s.title} (${s.durationMin}min, ${s.tokens} tokens)`);
}
lines.push('');
}

// Heartbeat section
lines.push('## Heartbeat');
lines.push(`- ${heartbeat.okCount} HEARTBEAT_OK`);
lines.push(`- ${heartbeat.okCount}x HEARTBEAT_OK`);
if (heartbeat.actions.length > 0) {
lines.push(`- ${heartbeat.actions.length}回 アクション実行:`);
lines.push(`- ${heartbeat.actions.length}x actions executed:`);
for (const action of heartbeat.actions) {
const time = toLocalTime(action.timestamp, timezone);
const tools = [...new Set(action.toolCalls.map((tc) => tc.name))].join(', ');
Expand All @@ -287,10 +287,10 @@ export async function generateDailySummary(
// Cron Jobs section
lines.push('## Cron Jobs');
if (cronStats.jobs.length === 0) {
lines.push('(cronジョブなし)', '');
lines.push('(No cron jobs)', '');
} else {
for (const job of cronStats.jobs) {
const status = job.errors > 0 ? `${job.runs - job.errors}/${job.runs} ✓` : `${job.runs}回実行 ✓`;
const status = job.errors > 0 ? `${job.runs - job.errors}/${job.runs} ✓` : `${job.runs}x runs ✓`;
lines.push(`- ${job.jobId}: ${status}`);
}
lines.push('');
Expand Down Expand Up @@ -562,9 +562,9 @@ async function summarizeHeartbeatActions(
.join('\n\n');

const prompt = [
'以下のハートビートアクション実行ログを、各エントリごとに「### HH:MM — カテゴリ」形式で要約してください。',
'各エントリは2-3文で、何を検知し何をしたかを簡潔に記述。',
'Markdown以外のメタ情報は出力しないでください。',
'Summarize the following heartbeat action execution logs, with each entry in "### HH:MM — Category" format.',
'Each entry should be 2-3 sentences, concisely describing what was detected and what action was taken.',
'Output only Markdown content — no meta-information.',
'',
entriesText,
].join('\n');
Expand Down Expand Up @@ -594,38 +594,38 @@ async function generateHighlights(
const dataParts: string[] = [];

if (sessions.length > 0) {
dataParts.push('セッション:');
dataParts.push('Sessions:');
for (const s of sessions) {
dataParts.push(`- ${s.trigger}: ${s.title}${s.durationMin}分)`);
dataParts.push(`- ${s.trigger}: ${s.title} (${s.durationMin}min)`);
}
}

if (heartbeat.actions.length > 0) {
dataParts.push('ハートビートアクション:');
dataParts.push('Heartbeat Actions:');
for (const a of heartbeat.actions) {
const time = a.timestamp.substring(11, 16);
dataParts.push(`- ${time}: ${a.responseText.substring(0, 200)}`);
}
}

if (cronStats.jobs.length > 0) {
dataParts.push('Cronジョブ:');
dataParts.push('Cron Jobs:');
for (const j of cronStats.jobs) {
dataParts.push(`- ${j.jobId}: ${j.runs}回実行`);
dataParts.push(`- ${j.jobId}: ${j.runs}x runs`);
}
}

if (dailyLogContent) {
dataParts.push('エージェントメモ(Daily Log:');
dataParts.push('Agent Notes (Daily Log):');
dataParts.push(dailyLogContent.substring(0, 2000));
}

// No data worth highlighting
if (dataParts.length === 0) return '';

const prompt = [
'以下の1日のアクティビティデータから、1-3文の簡潔な日本語ハイライトを生成してください。',
'重要なイベントや成果を中心に。Markdown不要、プレーンテキストのみ。',
'From the following daily activity data, generate a concise highlight in 1-3 sentences.',
'Focus on important events and accomplishments. Plain text only — no Markdown.',
'',
dataParts.join('\n'),
].join('\n');
Expand Down
2 changes: 1 addition & 1 deletion src/channels/chat-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ async function buildEventData(thread: Thread, message: Message): Promise<AgentRu
const workspacePath = getWorkspacePath(config);
const files = await downloadAttachments(message, sessionId, workspacePath);

// Fetch channel topic for per-channel behavior control (e.g. "日本語で応答")
// Fetch channel topic for per-channel behavior control (e.g. "respond in Japanese")
const channelTopic = thread.isDM ? undefined : await fetchChannelTopic(thread);

// Build channel conversation context (experimental)
Expand Down
20 changes: 9 additions & 11 deletions src/cli/commands/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ function abRun(args: string[]): boolean {
if (result.error) {
const err = result.error as NodeJS.ErrnoException;
if (err.code === 'ENOENT') {
process.stderr.write(
'agent-browser が見つかりません。bun i -g agent-browser でインストールしてください。\n',
);
process.stderr.write('agent-browser not found. Install it with: bun i -g agent-browser\n');
} else {
process.stderr.write(`agent-browser error: ${err.message}\n`);
}
Expand All @@ -61,24 +59,24 @@ export function registerBrowserCommand(program: Command): void {
spawnSync('agent-browser', ['--native', 'close'], { stdio: 'ignore' });

process.stderr.write(`Opening ${targetUrl} in headed browser...\n`);
process.stderr.write(' Google ログインは自動化検出制限により非対応です\n\n');
process.stderr.write('Note: Google login is not supported due to automation detection restrictions\n\n');

if (!abRun(['--native', '--headed', 'open', targetUrl])) {
process.exit(1);
}

await waitForEnter('ログインが完了したら Enter を押してください...');
await waitForEnter('Press Enter when login is complete...');

process.stderr.write('\n認証状態を保存中...\n');
process.stderr.write('\nSaving auth state...\n');
if (!abRun(['state', 'save', BROWSER_STATE_PATH])) {
process.stderr.write('state save に失敗しました。\n');
process.stderr.write('Failed to save state.\n');
process.exit(1);
}

spawnSync('agent-browser', ['--native', 'close'], { stdio: 'ignore' });

process.stderr.write(`\n保存先: ${BROWSER_STATE_PATH}\n`);
process.stderr.write('次回のエージェント実行時に自動的に認証状態が復元されます。\n');
process.stderr.write(`\nSaved to: ${BROWSER_STATE_PATH}\n`);
process.stderr.write('Auth state will be automatically restored on the next agent run.\n');
});

browserCmd
Expand All @@ -101,10 +99,10 @@ export function registerBrowserCommand(program: Command): void {
.description('Delete saved auth state')
.action(() => {
if (!existsSync(BROWSER_STATE_PATH)) {
process.stderr.write('認証状態ファイルが存在しません。\n');
process.stderr.write('Auth state file does not exist.\n');
return;
}
rmSync(BROWSER_STATE_PATH, { force: true });
process.stderr.write(`削除しました: ${BROWSER_STATE_PATH}\n`);
process.stderr.write(`Deleted: ${BROWSER_STATE_PATH}\n`);
});
}
2 changes: 1 addition & 1 deletion src/cli/commands/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export function registerRunCommand(program: Command): void {
const useInteractive = useTui && !prompt;

if (useInteractive && !config.setupCompleted) {
process.stderr.write('初回セットアップが未完了です。セットアップを開始します...\n');
process.stderr.write('Initial setup is not complete. Starting setup...\n');
const { runSetupWizard } = await import('./setup.js');
await runSetupWizard(config, workspacePath);
}
Expand Down
2 changes: 1 addition & 1 deletion src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ program.addCommand(buildSkillCommand());
// Show help when no command is given (default Commander behavior is silent exit)
if (process.argv.length <= 2) {
program.outputHelp();
process.stderr.write('\n はじめての方は geminiclaw setup を実行してください。\n\n');
process.stderr.write('\n New here? Run geminiclaw setup to get started.\n\n');
} else {
program.parse();
}
2 changes: 1 addition & 1 deletion src/config/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { Config } from './schema.js';

export const GEMINICLAW_HOME = join(homedir(), '.geminiclaw');
export const CONFIG_PATH = join(GEMINICLAW_HOME, 'config.json');
/** GeminiClaw固有のGemini CLI設定ファイル。~/.gemini/settings.jsonには書かない。 */
/** GeminiClaw-specific Gemini CLI settings file. Never written to ~/.gemini/settings.json. */
export const GEMINICLAW_SETTINGS_PATH = join(GEMINICLAW_HOME, 'settings.json');

export const BROWSER_PROFILE_DIR = join(GEMINICLAW_HOME, 'browser-profile');
Expand Down
22 changes: 11 additions & 11 deletions src/cron/cli.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/**
* cron/cli.ts — `geminiclaw cron` サブコマンド群。
* cron/cli.ts — `geminiclaw cron` subcommands.
*
* cron list / add / rm を提供する。
* jobs.json はソースオブトゥルースなので CLI は薄いラッパーに徹する。
* Provides cron list / add / rm commands.
* jobs.json is the source of truth; the CLI is a thin wrapper around the store.
*/

import { randomUUID } from 'node:crypto';
Expand All @@ -13,12 +13,12 @@ import { addJob, computeInitialNextRun, editJob, listJobs, loadRunLog, removeJob
import type { CronJob } from './types.js';

/**
* スケジュール文字列をパースする。
* Parse a schedule string into a schedule object.
*
* 対応フォーマット:
* - "every 30m" / "every 2h" EverySchedule
* - "at 2026-03-01T09:00:00" AtSchedule
* - "0 9 * * *" CronSchedule (5 フィールド)
* Supported formats:
* - "every 30m" / "every 2h" -> EverySchedule
* - "at 2026-03-01T09:00:00" -> AtSchedule
* - "0 9 * * *" -> CronSchedule (5 fields)
*/
function parseSchedule(input: string): CronJob['schedule'] {
const trimmed = input.trim();
Expand Down Expand Up @@ -68,7 +68,7 @@ function formatSchedule(job: CronJob): string {
}

/**
* `geminiclaw cron` コマンドツリーを構築して返す。
* Build and return the `geminiclaw cron` command tree.
*/
export function buildCronCommand(): Command {
const cron = new Command('cron').description('Cron job management');
Expand Down Expand Up @@ -99,7 +99,7 @@ export function buildCronCommand(): Command {
const status = j.enabled ? 'enabled ' : 'disabled';
const tz = j.timezone || config.timezone || undefined;
const next = j.nextRunAt
? new Date(j.nextRunAt).toLocaleString('ja-JP', tz ? { timeZone: tz } : undefined)
? new Date(j.nextRunAt).toLocaleString('en-US', tz ? { timeZone: tz } : undefined)
: '—';
const sched = formatSchedule(j);
process.stdout.write(
Expand Down Expand Up @@ -336,7 +336,7 @@ export function buildCronCommand(): Command {
const tz = config.timezone || undefined;
process.stdout.write(`Run history for ${id} (last ${entries.length}):\n\n`);
for (const e of entries) {
const time = new Date(e.timestamp).toLocaleString('ja-JP', tz ? { timeZone: tz } : undefined);
const time = new Date(e.timestamp).toLocaleString('en-US', tz ? { timeZone: tz } : undefined);
const reason = e.reason ? ` — ${e.reason}` : '';
process.stdout.write(` ${time} ${e.status}${reason}\n`);
}
Expand Down
22 changes: 11 additions & 11 deletions src/mcp/admin-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,14 @@ const TOOLS = [
];

/**
* skill install を直接ハンドルする。
* Handle skill install directly (not via CLI subprocess).
*
* フロー:
* 1. staging dir に bunx skills add(無害)
* 2. セキュリティスキャン
* 3. safe → 即座に workspace に移動
* 4. warning/danger findings 付きで ask_user → ユーザー判断
* 5. staging クリーンアップ
* Flow:
* 1. bunx skills add into a staging dir (harmless)
* 2. Run security scan
* 3. safe -> move to workspace immediately
* 4. warning/danger -> show findings via ask_user -> user decides
* 5. Clean up staging dir
*/
async function handleSkillInstall(
workspace: string,
Expand All @@ -172,7 +172,7 @@ async function handleSkillInstall(
const { installSkill, confirmInstall, cleanupStaging } = await import('../skills/manager.js');
const start = Date.now();

// args をパース: <ref> [--skill <name>] [--force]
// Parse args: <ref> [--skill <name>] [--force]
const ref = args.find((a) => !a.startsWith('-'));
if (!ref) {
return { content: [{ type: 'text', text: 'Error: missing skill source reference' }], isError: true };
Expand All @@ -190,7 +190,7 @@ async function handleSkillInstall(

const output: string[] = [];

// safe スキル: supervised モードではユーザー確認を挟む
// Safe skills: require user confirmation in supervised mode
if (result.scanned.length > 0) {
const names = result.scanned.join(', ');
await confirmIfNeeded(workspace, 'write', `Install safe skills: ${names}`);
Expand All @@ -199,7 +199,7 @@ async function handleSkillInstall(
}
}

// warned スキル: findings 付きで ask_user
// Warned skills: show findings via ask_user
if (result.warned.length > 0 && result._stagingDir) {
for (const name of result.warned) {
const report = result.reports[name];
Expand All @@ -222,7 +222,7 @@ async function handleSkillInstall(
}
}

// blocked スキル
// Blocked skills
for (const name of result.blocked) {
const report = result.reports[name];
const findingsText = report
Expand Down
Loading
Loading