feat: agent and CLI control plane for sessions, worktrees, and scheduled tasks (#2408)
Add a shared OpenChamber control service with two thin adapters — a native `openchamber` tool injected into managed OpenCode, and new CLI commands — so users can manage parallel sessions, worktrees, and scheduled tasks conversationally through agents or from the terminal. Control plane: - New openchamber-control service owning a fixed action contract: projects.list, models.list, session list/create/send/fork/status/messages, and schedule list/create/run/delete/toggle. Session and worktree deletion and project registration are deliberately not exposed. - New openchamber-sessions module owning create/worktree/prompt orchestration, Goal Mode dispatch, wait semantics (initial idle never counts as completion; timeout and cancellation are failures), and explicit partial-failure results. - Scheduled-task logic extracted into a service shared by routes, CLI, and the agent tool. Agent tool: - Managed OpenCode gets a materialized plugin registering one typed tool with a loopback-only callback, per-child ephemeral bearer (timing-safe, never persisted or logged), and abort propagation into the service. - The ~1.5k-token schema applies progressive disclosure: short descriptions, server-side validation returning actionable usage errors, and intent guardrails — created sessions/tasks are user-facing work (not age self-delegation); worktree/goal/agent/variant/wait are omit-by-default; dispatches produce no completion notification, and later result r to session.messages, which now returns the authoritative sessionStatus. - session.create without a user-named model picks from favorites/re send/fork omit the selection and the service reuses the target session's last user-message model, agent, and variant before falling back t - An "Agent control tool" setting (default on, Save + Reload to apply) disables plugin injection entirely. CLI: - New `openchamber session`, `schedule`, `projects`, and `models` commands with automatic instance targeting, --wait/--timeout/--last-assist worktree flags, and Goal Mode, preserving interactive, non-TTY, --quiet, and --json contracts. The control HTTP timeout derives from the w instead of the 4-second default. UI: - New built-in "Schedule a Task" starter (/schedule-task) running a dialogue that defines a task and offers to create it via the tool after explicit confirmation; Craft a Goal and Feature Planning gain the handoff offer, and guided starters reserve the question tool for concrete option choices. Localized in all 10 locales, migrated into custom starter lists, hidden on VS Code. - Sidebar shows CLI/agent-created sessions live via the control eve - openchamber tool calls render with per-action titles and metadata.
This commit is contained in:
committed by
GitHub
parent
484fe8bc18
commit
e908db637b
@@ -143,6 +143,7 @@ export type DesktopSettings = {
|
||||
desktopWindowControlsPosition?: DesktopWindowControlsPosition;
|
||||
inputSpellcheckEnabled?: boolean;
|
||||
showOpenCodeUpdateNotifications?: boolean;
|
||||
agentControlToolEnabled?: boolean;
|
||||
openCodeUpdateToastDismissedVersion?: string;
|
||||
showToolFileIcons?: boolean;
|
||||
codeBlockLineWrap?: boolean;
|
||||
@@ -208,6 +209,7 @@ export type DesktopSettings = {
|
||||
draftStarters?: DraftStarterRef[];
|
||||
// One-time migration marker: Craft a Goal was offered in the starter row.
|
||||
draftStartersCraftGoalAdded?: boolean;
|
||||
draftStartersScheduleTaskAdded?: boolean;
|
||||
};
|
||||
|
||||
type DesktopBridgeGlobal = {
|
||||
|
||||
@@ -28,6 +28,7 @@ export const BUILTIN_STARTERS: readonly BuiltInStarter[] = [
|
||||
{ name: 'weigh', icon: 'scales-3', labelKey: 'chat.draftPresets.weigh.label', command: '/weigh' },
|
||||
{ name: 'plan-feature', icon: 'survey', labelKey: 'chat.draftPresets.plan.label', command: '/plan-feature' },
|
||||
{ name: 'craft-goal', icon: 'target', labelKey: 'chat.draftPresets.craftGoal.label', command: '/craft-goal' },
|
||||
{ name: 'schedule-task', icon: 'calendar-schedule', labelKey: 'chat.draftPresets.scheduleTask.label', command: '/schedule-task' },
|
||||
{ name: 'debug', icon: 'bug', labelKey: 'chat.draftPresets.debug.label', command: '/debug' },
|
||||
{ name: 'review', icon: 'search-eye', labelKey: 'chat.draftPresets.review.label', command: '/workspace-review' },
|
||||
];
|
||||
|
||||
@@ -987,6 +987,9 @@ export const settingsDict = {
|
||||
'settings.openchamber.opencodeCli.field.binaryPathPlaceholder': '/Users/you/.bun/bin/opencode',
|
||||
'settings.openchamber.opencodeCli.field.showUpdateNotifications': 'Show OpenCode update notifications',
|
||||
'settings.openchamber.opencodeCli.field.showUpdateNotificationsAria': 'Show OpenCode update notifications',
|
||||
'settings.openchamber.opencodeCli.field.agentControlTool': 'Agent control tool',
|
||||
'settings.openchamber.opencodeCli.field.agentControlToolAria': 'Enable the agent control tool',
|
||||
'settings.openchamber.opencodeCli.field.agentControlToolInfo': 'Let agents orchestrate your work from chat: spin up sessions and worktrees, delegate prompts to other agents, and manage scheduled tasks. Adds a small tool description to each session. Applies after Save + Reload.',
|
||||
'settings.openchamber.opencodeCli.actions.browseAria': 'Browse for OpenCode binary path',
|
||||
'settings.openchamber.opencodeCli.actions.browse': 'Browse',
|
||||
'settings.openchamber.opencodeCli.actions.saveAndReload': 'Save + Reload',
|
||||
|
||||
@@ -1759,6 +1759,7 @@ export const dict = {
|
||||
'chat.draftPresets.weigh.label': 'Weigh my options',
|
||||
'chat.draftPresets.plan.label': 'Start feature planning',
|
||||
'chat.draftPresets.craftGoal.label': 'Craft a Goal',
|
||||
'chat.draftPresets.scheduleTask.label': 'Schedule a Task',
|
||||
'chat.draftPresets.debug.label': 'Debug an issue',
|
||||
'chat.draftPresets.review.label': 'Review my changes',
|
||||
'chat.draftStarters.add': 'Add a starter',
|
||||
@@ -1835,7 +1836,9 @@ export const dict = {
|
||||
'chat.commandAutocomplete.command.handoffReviewDescription': 'Create or reuse a separate review session from a generated handoff.',
|
||||
'chat.commandAutocomplete.command.featurePlanDescription': 'Start a guided, back-and-forth planning session for a new feature.',
|
||||
'chat.commandAutocomplete.command.craftGoalDescription': 'Turn an idea or task into a clear, verifiable Goal.',
|
||||
'chat.commandAutocomplete.command.scheduleTaskDescription': 'Define a scheduled task through a guided dialogue.',
|
||||
'chat.chatInput.toast.craftGoalFailed': 'Failed to start Goal crafting',
|
||||
'chat.chatInput.toast.scheduleTaskFailed': 'Failed to start scheduled task setup',
|
||||
'chat.commandAutocomplete.command.catchUpDescription': 'Re-establish context: what you were doing and where to pick up.',
|
||||
'chat.commandAutocomplete.command.debugDescription': 'Guided root-cause investigation for a bug before proposing a fix.',
|
||||
'chat.commandAutocomplete.command.weighDescription': 'Weigh 2-3 approaches with trade-offs and a recommendation before you commit.',
|
||||
|
||||
@@ -954,6 +954,9 @@ export const settingsDict = {
|
||||
"settings.openchamber.opencodeCli.field.binaryPathPlaceholder": "/Users/you/.bun/bin/opencode",
|
||||
"settings.openchamber.opencodeCli.field.showUpdateNotifications": "Mostrar notificaciones de actualización de OpenCode",
|
||||
"settings.openchamber.opencodeCli.field.showUpdateNotificationsAria": "Mostrar notificaciones de actualización de OpenCode",
|
||||
"settings.openchamber.opencodeCli.field.agentControlTool": "Herramienta de control para agentes",
|
||||
"settings.openchamber.opencodeCli.field.agentControlToolAria": "Activar la herramienta de control para agentes",
|
||||
"settings.openchamber.opencodeCli.field.agentControlToolInfo": "Permite que los agentes orquesten tu trabajo desde el chat: crear sesiones y worktrees, delegar prompts a otros agentes y gestionar tareas programadas. Añade una pequeña descripción de herramienta a cada sesión. Se aplica tras Save + Reload.",
|
||||
"settings.openchamber.opencodeCli.actions.browseAria": "Buscar ruta del ejecutable de OpenCode",
|
||||
"settings.openchamber.opencodeCli.actions.browse": "Buscar",
|
||||
"settings.openchamber.opencodeCli.actions.saveAndReload": "Guardar + Recargar",
|
||||
|
||||
@@ -1737,6 +1737,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"chat.draftPresets.weigh.label": "Weigh my options",
|
||||
"chat.draftPresets.plan.label": "Start feature planning",
|
||||
"chat.draftPresets.craftGoal.label": "Crear un Goal",
|
||||
"chat.draftPresets.scheduleTask.label": "Programar una tarea",
|
||||
"chat.draftPresets.debug.label": "Debug an issue",
|
||||
"chat.draftPresets.review.label": "Review my changes",
|
||||
"chat.draftStarters.add": "Add a starter",
|
||||
@@ -1813,7 +1814,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
"chat.commandAutocomplete.command.handoffReviewDescription": "Crea o reutiliza una sesión de revisión separada a partir de un handoff generado.",
|
||||
"chat.commandAutocomplete.command.featurePlanDescription": "Inicia una sesión de planificación guiada e interactiva para una nueva función.",
|
||||
"chat.commandAutocomplete.command.craftGoalDescription": "Convierte una idea o tarea en un Goal claro y verificable.",
|
||||
"chat.commandAutocomplete.command.scheduleTaskDescription": "Define una tarea programada mediante un diálogo guiado.",
|
||||
"chat.chatInput.toast.craftGoalFailed": "No se pudo iniciar la creación del Goal",
|
||||
"chat.chatInput.toast.scheduleTaskFailed": "No se pudo iniciar la configuración de la tarea programada",
|
||||
"chat.commandAutocomplete.command.catchUpDescription": "Recupera el contexto: qué estabas haciendo y por dónde continuar.",
|
||||
"chat.commandAutocomplete.command.debugDescription": "Investigación guiada de la causa raíz de un error antes de proponer una solución.",
|
||||
"chat.commandAutocomplete.command.weighDescription": "Compara 2-3 enfoques con sus ventajas y desventajas y una recomendación antes de decidir.",
|
||||
|
||||
@@ -875,6 +875,9 @@ export const settingsDict = {
|
||||
'settings.openchamber.opencodeCli.field.binaryPathPlaceholder': '/Users/you/.bun/bin/opencode',
|
||||
'settings.openchamber.opencodeCli.field.showUpdateNotifications': 'Afficher les notifications de mise à jour de OpenCode',
|
||||
'settings.openchamber.opencodeCli.field.showUpdateNotificationsAria': 'Afficher les notifications de mise à jour de OpenCode',
|
||||
'settings.openchamber.opencodeCli.field.agentControlTool': 'Outil de contrôle pour les agents',
|
||||
'settings.openchamber.opencodeCli.field.agentControlToolAria': 'Activer l’outil de contrôle pour les agents',
|
||||
'settings.openchamber.opencodeCli.field.agentControlToolInfo': 'Laissez les agents orchestrer votre travail depuis le chat : créer des sessions et des worktrees, déléguer des prompts à d’autres agents et gérer les tâches planifiées. Ajoute une courte description d’outil à chaque session. Appliqué après Save + Reload.',
|
||||
'settings.openchamber.opencodeCli.actions.browseAria': 'Rechercher le chemin binaire OpenCode',
|
||||
'settings.openchamber.opencodeCli.actions.browse': 'Parcourir',
|
||||
'settings.openchamber.opencodeCli.actions.saveAndReload': 'Enregistrer + Recharger',
|
||||
|
||||
@@ -2750,6 +2750,7 @@ export const dict = {
|
||||
'chat.draftPresets.weigh.label': 'Comparer mes options',
|
||||
'chat.draftPresets.plan.label': 'Lancer la planification de fonctionnalité',
|
||||
'chat.draftPresets.craftGoal.label': 'Créer un Goal',
|
||||
'chat.draftPresets.scheduleTask.label': 'Planifier une tâche',
|
||||
'chat.draftPresets.debug.label': 'Déboguer un problème',
|
||||
'chat.draftPresets.review.label': 'Revoir mes modifications',
|
||||
'chat.draftStarters.add': 'Ajouter un démarrage',
|
||||
@@ -2762,7 +2763,9 @@ export const dict = {
|
||||
'chat.commandAutocomplete.command.handoffReviewDescription': 'Créer ou réutiliser une session de revue séparée à partir d’un handoff généré.',
|
||||
'chat.commandAutocomplete.command.featurePlanDescription': 'Lancer une session guidée et interactive de planification pour une nouvelle fonctionnalité.',
|
||||
'chat.commandAutocomplete.command.craftGoalDescription': 'Transformer une idée ou une tâche en Goal clair et vérifiable.',
|
||||
'chat.commandAutocomplete.command.scheduleTaskDescription': 'Définir une tâche planifiée via un dialogue guidé.',
|
||||
'chat.chatInput.toast.craftGoalFailed': 'Impossible de démarrer la création du Goal',
|
||||
'chat.chatInput.toast.scheduleTaskFailed': 'Impossible de démarrer la configuration de la tâche planifiée',
|
||||
'chat.commandAutocomplete.command.catchUpDescription': 'Rétablir le contexte : ce que vous faisiez et où reprendre.',
|
||||
'chat.commandAutocomplete.command.debugDescription': 'Investigation guidée de la cause racine d’un bug avant de proposer une correction.',
|
||||
'chat.commandAutocomplete.command.weighDescription': 'Comparer 2 à 3 approches avec compromis et recommandation avant de vous engager.',
|
||||
|
||||
@@ -987,6 +987,9 @@ export const settingsDict = {
|
||||
'settings.openchamber.opencodeCli.field.binaryPathPlaceholder': '/Users/you/.bun/bin/opencode',
|
||||
'settings.openchamber.opencodeCli.field.showUpdateNotifications': 'OpenCode のアップデート通知を表示',
|
||||
'settings.openchamber.opencodeCli.field.showUpdateNotificationsAria': 'OpenCode のアップデート通知を表示',
|
||||
'settings.openchamber.opencodeCli.field.agentControlTool': 'エージェント制御ツール',
|
||||
'settings.openchamber.opencodeCli.field.agentControlToolAria': 'エージェント制御ツールを有効にする',
|
||||
'settings.openchamber.opencodeCli.field.agentControlToolInfo': 'エージェントがチャットから作業をオーケストレーションできるようにします。セッションや worktree の作成、他のエージェントへのプロンプト委任、スケジュールタスクの管理が可能です。各セッションに小さなツール説明が追加されます。Save + Reload 後に適用されます。',
|
||||
'settings.openchamber.opencodeCli.actions.browseAria': 'OpenCode バイナリパスを参照',
|
||||
'settings.openchamber.opencodeCli.actions.browse': '参照',
|
||||
'settings.openchamber.opencodeCli.actions.saveAndReload': '保存して再読み込み',
|
||||
|
||||
@@ -1755,6 +1755,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.draftPresets.weigh.label': '選択肢を比較検討',
|
||||
'chat.draftPresets.plan.label': '機能の計画を開始',
|
||||
'chat.draftPresets.craftGoal.label': 'Goal を作成',
|
||||
'chat.draftPresets.scheduleTask.label': 'タスクをスケジュール',
|
||||
'chat.draftPresets.debug.label': '問題をデバッグ',
|
||||
'chat.draftPresets.review.label': '変更をレビュー',
|
||||
'chat.draftStarters.add': 'スターターを追加',
|
||||
@@ -1831,7 +1832,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.commandAutocomplete.command.handoffReviewDescription': '生成されたハンドオフから別のレビューセッションを作成または再利用。',
|
||||
'chat.commandAutocomplete.command.featurePlanDescription': '新機能のガイド付き対話型計画セッションを開始。',
|
||||
'chat.commandAutocomplete.command.craftGoalDescription': 'アイデアやタスクを明確で検証可能な Goal に変換します。',
|
||||
'chat.commandAutocomplete.command.scheduleTaskDescription': '対話形式でスケジュールタスクを定義します。',
|
||||
'chat.chatInput.toast.craftGoalFailed': 'Goal の作成を開始できませんでした',
|
||||
'chat.chatInput.toast.scheduleTaskFailed': 'スケジュールタスクの設定を開始できませんでした',
|
||||
'chat.commandAutocomplete.command.catchUpDescription': 'コンテキストを再確立: 何をしていたか、どこから再開するか。',
|
||||
'chat.commandAutocomplete.command.debugDescription': '修正を提案する前に、バグのガイド付き根本原因調査。',
|
||||
'chat.commandAutocomplete.command.weighDescription': 'トレードオフと推奨事項を含む2~3のアプローチを比較検討してからコミット。',
|
||||
|
||||
@@ -954,6 +954,9 @@ export const settingsDict = {
|
||||
'settings.openchamber.opencodeCli.field.binaryPathPlaceholder': '/Users/you/.bun/bin/opencode',
|
||||
'settings.openchamber.opencodeCli.field.showUpdateNotifications': 'OpenCode 업데이트 알림 표시',
|
||||
'settings.openchamber.opencodeCli.field.showUpdateNotificationsAria': 'OpenCode 업데이트 알림 표시',
|
||||
'settings.openchamber.opencodeCli.field.agentControlTool': '에이전트 제어 도구',
|
||||
'settings.openchamber.opencodeCli.field.agentControlToolAria': '에이전트 제어 도구 활성화',
|
||||
'settings.openchamber.opencodeCli.field.agentControlToolInfo': '에이전트가 채팅에서 작업을 오케스트레이션할 수 있습니다. 세션과 worktree 생성, 다른 에이전트에게 프롬프트 위임, 예약 작업 관리가 가능합니다. 각 세션에 작은 도구 설명이 추가됩니다. Save + Reload 후 적용됩니다.',
|
||||
'settings.openchamber.opencodeCli.actions.browseAria': 'OpenCode binary 경로 찾아보기',
|
||||
'settings.openchamber.opencodeCli.actions.browse': '찾아보기',
|
||||
'settings.openchamber.opencodeCli.actions.saveAndReload': '저장 + 다시 로드',
|
||||
|
||||
@@ -1761,6 +1761,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.draftPresets.weigh.label': 'Weigh my options',
|
||||
'chat.draftPresets.plan.label': 'Start feature planning',
|
||||
'chat.draftPresets.craftGoal.label': 'Goal 만들기',
|
||||
'chat.draftPresets.scheduleTask.label': '작업 예약하기',
|
||||
'chat.draftPresets.debug.label': 'Debug an issue',
|
||||
'chat.draftPresets.review.label': 'Review my changes',
|
||||
'chat.draftStarters.add': 'Add a starter',
|
||||
@@ -1837,7 +1838,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.commandAutocomplete.command.handoffReviewDescription': '생성된 인수인계로 별도의 리뷰 세션을 만들거나 재사용합니다.',
|
||||
'chat.commandAutocomplete.command.featurePlanDescription': '새 기능을 위한 대화형 가이드 계획 세션을 시작합니다.',
|
||||
'chat.commandAutocomplete.command.craftGoalDescription': '아이디어나 작업을 명확하고 검증 가능한 Goal로 만듭니다.',
|
||||
'chat.commandAutocomplete.command.scheduleTaskDescription': '안내형 대화를 통해 예약 작업을 정의합니다.',
|
||||
'chat.chatInput.toast.craftGoalFailed': 'Goal 만들기를 시작하지 못했습니다',
|
||||
'chat.chatInput.toast.scheduleTaskFailed': '예약 작업 설정을 시작하지 못했습니다',
|
||||
'chat.commandAutocomplete.command.catchUpDescription': '맥락을 다시 파악합니다: 무엇을 하고 있었고 어디서 이어서 할지.',
|
||||
'chat.commandAutocomplete.command.debugDescription': '수정안을 제시하기 전에 버그의 근본 원인을 단계적으로 조사합니다.',
|
||||
'chat.commandAutocomplete.command.weighDescription': '결정하기 전에 2~3가지 접근 방식을 장단점과 함께 비교하고 추천을 제시합니다.',
|
||||
|
||||
@@ -833,6 +833,9 @@ export const settingsDict = {
|
||||
'settings.openchamber.opencodeCli.field.binaryPathPlaceholder': '/Users/you/.bun/bin/opencode',
|
||||
'settings.openchamber.opencodeCli.field.showUpdateNotifications': 'Pokazuj powiadomienia o aktualizacjach OpenCode',
|
||||
'settings.openchamber.opencodeCli.field.showUpdateNotificationsAria': 'Pokazuj powiadomienia o aktualizacjach OpenCode',
|
||||
'settings.openchamber.opencodeCli.field.agentControlTool': 'Narzędzie sterowania dla agentów',
|
||||
'settings.openchamber.opencodeCli.field.agentControlToolAria': 'Włącz narzędzie sterowania dla agentów',
|
||||
'settings.openchamber.opencodeCli.field.agentControlToolInfo': 'Pozwól agentom orkiestrować Twoją pracę z czatu: tworzyć sesje i worktree, delegować prompty innym agentom oraz zarządzać zaplanowanymi zadaniami. Dodaje krótki opis narzędzia do każdej sesji. Zastosowane po Save + Reload.',
|
||||
'settings.openchamber.opencodeCli.tipMiddle': 'zmienna środowiskowa, ale to ustawienie jest zapisywane w',
|
||||
'settings.openchamber.opencodeCli.tipPrefix': 'Wskazówka: możesz również użyć',
|
||||
'settings.openchamber.opencodeCli.title': 'OpenCode CLI',
|
||||
|
||||
@@ -649,6 +649,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.draftPresets.weigh.label': 'Weigh my options',
|
||||
'chat.draftPresets.plan.label': 'Start feature planning',
|
||||
'chat.draftPresets.craftGoal.label': 'Utwórz Goal',
|
||||
'chat.draftPresets.scheduleTask.label': 'Zaplanuj zadanie',
|
||||
'chat.draftPresets.debug.label': 'Debug an issue',
|
||||
'chat.draftPresets.review.label': 'Review my changes',
|
||||
'chat.draftStarters.add': 'Add a starter',
|
||||
@@ -724,7 +725,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.commandAutocomplete.command.handoffReviewDescription': 'Utwórz lub użyj ponownie osobnej sesji przeglądu z wygenerowanego handoffu.',
|
||||
'chat.commandAutocomplete.command.featurePlanDescription': 'Rozpocznij prowadzoną, interaktywną sesję planowania nowej funkcji.',
|
||||
'chat.commandAutocomplete.command.craftGoalDescription': 'Przekształć pomysł lub zadanie w jasny, weryfikowalny Goal.',
|
||||
'chat.commandAutocomplete.command.scheduleTaskDescription': 'Zdefiniuj zaplanowane zadanie w prowadzonym dialogu.',
|
||||
'chat.chatInput.toast.craftGoalFailed': 'Nie udało się rozpocząć tworzenia Goal',
|
||||
'chat.chatInput.toast.scheduleTaskFailed': 'Nie udało się rozpocząć konfiguracji zaplanowanego zadania',
|
||||
'chat.commandAutocomplete.command.catchUpDescription': 'Przywróć kontekst: nad czym pracowałeś i od czego kontynuować.',
|
||||
'chat.commandAutocomplete.command.debugDescription': 'Prowadzone badanie pierwotnej przyczyny błędu przed zaproponowaniem poprawki.',
|
||||
'chat.commandAutocomplete.command.weighDescription': 'Rozważ 2-3 podejścia z kompromisami i rekomendacją, zanim się zdecydujesz.',
|
||||
|
||||
@@ -954,6 +954,9 @@ export const settingsDict = {
|
||||
"settings.openchamber.opencodeCli.field.binaryPathPlaceholder": "/Users/you/.bun/bin/opencode",
|
||||
"settings.openchamber.opencodeCli.field.showUpdateNotifications": "Mostrar notificações de atualização do OpenCode",
|
||||
"settings.openchamber.opencodeCli.field.showUpdateNotificationsAria": "Mostrar notificações de atualização do OpenCode",
|
||||
"settings.openchamber.opencodeCli.field.agentControlTool": "Ferramenta de controle para agentes",
|
||||
"settings.openchamber.opencodeCli.field.agentControlToolAria": "Ativar a ferramenta de controle para agentes",
|
||||
"settings.openchamber.opencodeCli.field.agentControlToolInfo": "Permita que agentes orquestrem seu trabalho pelo chat: criar sessões e worktrees, delegar prompts a outros agentes e gerenciar tarefas agendadas. Adiciona uma pequena descrição de ferramenta a cada sessão. Aplicado após Save + Reload.",
|
||||
"settings.openchamber.opencodeCli.actions.browseAria": "Buscar caminho do executável do OpenCode",
|
||||
"settings.openchamber.opencodeCli.actions.browse": "Buscar",
|
||||
"settings.openchamber.opencodeCli.actions.saveAndReload": "Salvar + Recarregar",
|
||||
|
||||
@@ -1737,6 +1737,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"chat.draftPresets.weigh.label": "Weigh my options",
|
||||
"chat.draftPresets.plan.label": "Start feature planning",
|
||||
"chat.draftPresets.craftGoal.label": "Criar um Goal",
|
||||
"chat.draftPresets.scheduleTask.label": "Agendar uma tarefa",
|
||||
"chat.draftPresets.debug.label": "Debug an issue",
|
||||
"chat.draftPresets.review.label": "Review my changes",
|
||||
"chat.draftStarters.add": "Add a starter",
|
||||
@@ -1813,7 +1814,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
"chat.commandAutocomplete.command.handoffReviewDescription": "Crie ou reutilize uma sessão separada de revisão a partir de um handoff gerado.",
|
||||
"chat.commandAutocomplete.command.featurePlanDescription": "Inicie uma sessão de planejamento guiada e interativa para uma nova funcionalidade.",
|
||||
"chat.commandAutocomplete.command.craftGoalDescription": "Transforme uma ideia ou tarefa em um Goal claro e verificável.",
|
||||
"chat.commandAutocomplete.command.scheduleTaskDescription": "Defina uma tarefa agendada por meio de um diálogo guiado.",
|
||||
"chat.chatInput.toast.craftGoalFailed": "Não foi possível iniciar a criação do Goal",
|
||||
"chat.chatInput.toast.scheduleTaskFailed": "Não foi possível iniciar a configuração da tarefa agendada",
|
||||
"chat.commandAutocomplete.command.catchUpDescription": "Retome o contexto: o que você estava fazendo e por onde continuar.",
|
||||
"chat.commandAutocomplete.command.debugDescription": "Investigação guiada da causa raiz de um bug antes de propor uma correção.",
|
||||
"chat.commandAutocomplete.command.weighDescription": "Compare 2-3 abordagens com seus prós e contras e uma recomendação antes de decidir.",
|
||||
|
||||
@@ -954,6 +954,9 @@ export const settingsDict = {
|
||||
"settings.openchamber.opencodeCli.field.binaryPathPlaceholder": "/Users/you/.bun/bin/opencode",
|
||||
"settings.openchamber.opencodeCli.field.showUpdateNotifications": "Показувати сповіщення про оновлення OpenCode",
|
||||
"settings.openchamber.opencodeCli.field.showUpdateNotificationsAria": "Показувати сповіщення про оновлення OpenCode",
|
||||
"settings.openchamber.opencodeCli.field.agentControlTool": "Інструмент керування для агентів",
|
||||
"settings.openchamber.opencodeCli.field.agentControlToolAria": "Увімкнути інструмент керування для агентів",
|
||||
"settings.openchamber.opencodeCli.field.agentControlToolInfo": "Дозвольте агентам оркеструвати вашу роботу з чату: створювати сесії та worktree, делегувати запити іншим агентам і керувати запланованими задачами. Додає невеликий опис інструмента до кожної сесії. Застосовується після Save + Reload.",
|
||||
"settings.openchamber.opencodeCli.actions.browseAria": "Вибрати шлях до виконуваного файла OpenCode",
|
||||
"settings.openchamber.opencodeCli.actions.browse": "Огляд",
|
||||
"settings.openchamber.opencodeCli.actions.saveAndReload": "Зберегти й перезавантажити",
|
||||
|
||||
@@ -1737,6 +1737,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"chat.draftPresets.weigh.label": "Зважити варіанти",
|
||||
"chat.draftPresets.plan.label": "Розпочати планування фічі",
|
||||
"chat.draftPresets.craftGoal.label": "Сформувати Goal",
|
||||
"chat.draftPresets.scheduleTask.label": "Запланувати задачу",
|
||||
"chat.draftPresets.debug.label": "Дебаг проблеми",
|
||||
"chat.draftPresets.review.label": "Переглянути мої зміни",
|
||||
"chat.draftStarters.add": "Додати стартер",
|
||||
@@ -1813,7 +1814,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
"chat.commandAutocomplete.command.handoffReviewDescription": "Створити або повторно використати окрему сесію ревʼю зі згенерованого handoff.",
|
||||
"chat.commandAutocomplete.command.featurePlanDescription": "Розпочати покрокову діалогову сесію планування нової фічі.",
|
||||
"chat.commandAutocomplete.command.craftGoalDescription": "Перетворити ідею або завдання на чіткий Goal, який можна перевірити.",
|
||||
"chat.commandAutocomplete.command.scheduleTaskDescription": "Визначити заплановану задачу через керований діалог.",
|
||||
"chat.chatInput.toast.craftGoalFailed": "Не вдалося розпочати формування Goal",
|
||||
"chat.chatInput.toast.scheduleTaskFailed": "Не вдалося розпочати налаштування запланованої задачі",
|
||||
"chat.commandAutocomplete.command.catchUpDescription": "Повернутись у контекст: над чим працювали і звідки продовжити.",
|
||||
"chat.commandAutocomplete.command.debugDescription": "Кероване дослідження першопричини бага перед тим, як пропонувати фікс.",
|
||||
"chat.commandAutocomplete.command.weighDescription": "Зважити 2-3 підходи з trade-offs і рекомендацією перш ніж братися до роботи.",
|
||||
|
||||
@@ -954,6 +954,9 @@ export const settingsDict = {
|
||||
'settings.openchamber.opencodeCli.field.binaryPathPlaceholder': '/Users/you/.bun/bin/opencode',
|
||||
'settings.openchamber.opencodeCli.field.showUpdateNotifications': '显示 OpenCode 更新通知',
|
||||
'settings.openchamber.opencodeCli.field.showUpdateNotificationsAria': '显示 OpenCode 更新通知',
|
||||
'settings.openchamber.opencodeCli.field.agentControlTool': '智能体控制工具',
|
||||
'settings.openchamber.opencodeCli.field.agentControlToolAria': '启用智能体控制工具',
|
||||
'settings.openchamber.opencodeCli.field.agentControlToolInfo': '让智能体在聊天中编排你的工作:创建会话和 worktree、将提示委派给其他智能体、管理计划任务。会为每个会话添加少量工具说明。在 Save + Reload 后生效。',
|
||||
'settings.openchamber.opencodeCli.actions.browseAria': '浏览 OpenCode 可执行文件路径',
|
||||
'settings.openchamber.opencodeCli.actions.browse': '浏览',
|
||||
'settings.openchamber.opencodeCli.actions.saveAndReload': '保存并重载',
|
||||
|
||||
@@ -1725,6 +1725,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.draftPresets.weigh.label': 'Weigh my options',
|
||||
'chat.draftPresets.plan.label': 'Start feature planning',
|
||||
'chat.draftPresets.craftGoal.label': '创建 Goal',
|
||||
'chat.draftPresets.scheduleTask.label': '安排计划任务',
|
||||
'chat.draftPresets.debug.label': 'Debug an issue',
|
||||
'chat.draftPresets.review.label': 'Review my changes',
|
||||
'chat.draftStarters.add': 'Add a starter',
|
||||
@@ -1801,7 +1802,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.commandAutocomplete.command.handoffReviewDescription': '根据生成的交接内容创建或复用独立的审查会话。',
|
||||
'chat.commandAutocomplete.command.featurePlanDescription': '为新功能开始一次引导式的来回规划会话。',
|
||||
'chat.commandAutocomplete.command.craftGoalDescription': '将想法或任务转化为清晰且可验证的 Goal。',
|
||||
'chat.commandAutocomplete.command.scheduleTaskDescription': '通过引导式对话定义计划任务。',
|
||||
'chat.chatInput.toast.craftGoalFailed': '无法开始创建 Goal',
|
||||
'chat.chatInput.toast.scheduleTaskFailed': '无法开始设置计划任务',
|
||||
'chat.commandAutocomplete.command.catchUpDescription': '重新进入上下文:你之前在做什么、从哪里继续。',
|
||||
'chat.commandAutocomplete.command.debugDescription': '在提出修复方案前,引导式地排查 bug 的根本原因。',
|
||||
'chat.commandAutocomplete.command.weighDescription': '在动手前,权衡 2-3 种方案的利弊并给出推荐。',
|
||||
|
||||
@@ -928,6 +928,9 @@
|
||||
'settings.openchamber.opencodeCli.field.binaryPathPlaceholder': '/Users/you/.bun/bin/opencode',
|
||||
'settings.openchamber.opencodeCli.field.showUpdateNotifications': '顯示 OpenCode 更新通知',
|
||||
'settings.openchamber.opencodeCli.field.showUpdateNotificationsAria': '顯示 OpenCode 更新通知',
|
||||
'settings.openchamber.opencodeCli.field.agentControlTool': '智慧代理控制工具',
|
||||
'settings.openchamber.opencodeCli.field.agentControlToolAria': '啟用智慧代理控制工具',
|
||||
'settings.openchamber.opencodeCli.field.agentControlToolInfo': '讓代理從聊天中協調你的工作:建立工作階段與 worktree、將提示委派給其他代理、管理排程任務。會為每個工作階段加入少量工具說明。在 Save + Reload 後生效。',
|
||||
'settings.openchamber.opencodeCli.actions.browseAria': '瀏覽 OpenCode 可執行檔路徑',
|
||||
'settings.openchamber.opencodeCli.actions.browse': '瀏覽',
|
||||
'settings.openchamber.opencodeCli.actions.saveAndReload': '儲存並重新載入',
|
||||
|
||||
@@ -1729,6 +1729,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.draftPresets.weigh.label': 'Weigh my options',
|
||||
'chat.draftPresets.plan.label': 'Start feature planning',
|
||||
'chat.draftPresets.craftGoal.label': '建立 Goal',
|
||||
'chat.draftPresets.scheduleTask.label': '排程任務',
|
||||
'chat.draftPresets.debug.label': 'Debug an issue',
|
||||
'chat.draftPresets.review.label': 'Review my changes',
|
||||
'chat.draftStarters.add': 'Add a starter',
|
||||
@@ -1805,7 +1806,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.commandAutocomplete.command.handoffReviewDescription': '根據生成的交接內容建立或重用獨立的審查會話。',
|
||||
'chat.commandAutocomplete.command.featurePlanDescription': '為新功能開始一次引導式的來回規劃工作階段。',
|
||||
'chat.commandAutocomplete.command.craftGoalDescription': '將想法或任務轉化為清晰且可驗證的 Goal。',
|
||||
'chat.commandAutocomplete.command.scheduleTaskDescription': '透過引導式對話定義排程任務。',
|
||||
'chat.chatInput.toast.craftGoalFailed': '無法開始建立 Goal',
|
||||
'chat.chatInput.toast.scheduleTaskFailed': '無法開始設定排程任務',
|
||||
'chat.commandAutocomplete.command.catchUpDescription': '重新進入上下文:你之前在做什麼、從哪裡繼續。',
|
||||
'chat.commandAutocomplete.command.debugDescription': '在提出修復方案前,引導式地排查 bug 的根本原因。',
|
||||
'chat.commandAutocomplete.command.weighDescription': '在動手前,權衡 2-3 種方案的利弊並給出推薦。',
|
||||
|
||||
@@ -39,6 +39,8 @@ export type MagicPromptId =
|
||||
| 'session.plan.instructions'
|
||||
| 'session.craftGoal.visible'
|
||||
| 'session.craftGoal.instructions'
|
||||
| 'session.scheduleTask.visible'
|
||||
| 'session.scheduleTask.instructions'
|
||||
| 'session.catchup.visible'
|
||||
| 'session.catchup.instructions'
|
||||
| 'session.debug.visible'
|
||||
@@ -709,7 +711,7 @@ Please review the latest state again and report any remaining issues.
|
||||
|
||||
Run this as a dialogue, not a one-shot answer.
|
||||
|
||||
Whenever you ask the user a question, use the \`question\` tool. Do not ask questions in plain assistant text.
|
||||
Use the \`question\` tool only for clarifying decisions that have a small set of concrete answer options you already know from the conversation or your investigation — choices like option A/B/C, scope boundaries, or edge-case behavior. Ask open-ended questions, including what the user wants in the first place, in plain assistant text. Never invent speculative options just to fit the question tool.
|
||||
|
||||
1. Understand before asking. Once the user describes the idea, first investigate the codebase yourself — read the relevant files, existing patterns, data flow, and constraints. Ground every question in what the code actually shows, not in assumptions.
|
||||
|
||||
@@ -723,6 +725,8 @@ Whenever you ask the user a question, use the \`question\` tool. Do not ask ques
|
||||
|
||||
6. When everything is settled, produce the final implementation plan: a clear, ordered breakdown of the work, the files and areas affected, the decisions that were made (and why), known risks, and any remaining assumptions flagged explicitly. The plan must reflect the user's actual answers — never fill gaps with guesses.
|
||||
|
||||
7. After presenting the plan, if the \`openchamber\` tool is available, offer to start a separate session yourself to implement it; do so only after the user explicitly confirms, and include the full plan in that session's prompt because the new session cannot see this conversation.
|
||||
|
||||
Respond in the same language the user uses.`,
|
||||
},
|
||||
{
|
||||
@@ -746,9 +750,9 @@ A Goal is a persistent completion contract, not an implementation plan and not a
|
||||
|
||||
Run this as a guided dialogue, not a one-shot answer.
|
||||
|
||||
Whenever you ask the user a question, use the \`question\` tool. Do not ask questions in plain assistant text.
|
||||
Use the \`question\` tool only for clarifying decisions that have a small set of concrete answer options you already know from the conversation or your investigation — choices like option A/B/C, scope boundaries, or edge-case behavior. Ask open-ended questions, including what the user wants in the first place, in plain assistant text. Never invent speculative options just to fit the question tool.
|
||||
|
||||
1. Start from the user's intent. If the visible message includes an initial idea, use it immediately. Otherwise ask what they want to accomplish. Do not ask them to formulate the Goal themselves.
|
||||
1. Start from the user's intent. If the visible message includes an initial idea, use it immediately. Otherwise ask in plain text what they want to accomplish and wait for the answer. Do not ask them to formulate the Goal themselves.
|
||||
|
||||
2. Investigate before asking when context is available. For repository work, inspect relevant code, tests, scripts, documentation, and conventions when that would answer questions or expose constraints. Do not ask for information that can be determined reliably from the workspace.
|
||||
|
||||
@@ -790,7 +794,46 @@ Whenever you ask the user a question, use the \`question\` tool. Do not ask ques
|
||||
|
||||
The proposed Goal should normally be one compact paragraph. Keep enough operational detail to make completion auditable, but remove conversational history, rationale, repetition, and implementation details that are not part of the completion contract.
|
||||
|
||||
Do not activate, execute, or claim completion of the proposed Goal. End by inviting the user to revise it or use it in the Goal dialog.
|
||||
Do not activate, execute, or claim completion of the proposed Goal. End by inviting the user to revise it or use it in the Goal dialog. If the \`openchamber\` tool is available, also offer to start a new Goal session for it yourself; do so only after the user explicitly confirms.
|
||||
|
||||
Respond in the same language the user uses.`,
|
||||
},
|
||||
{
|
||||
id: 'session.scheduleTask.visible',
|
||||
title: 'Scheduled Task Visible Prompt',
|
||||
group: 'Session',
|
||||
description: 'Visible user message sent by the /schedule-task command.',
|
||||
placeholders: [
|
||||
{ key: 'idea_block', description: 'Optional initial automation idea supplied after the command.' },
|
||||
],
|
||||
template: `Help me set up a scheduled task.{{idea_block}}`,
|
||||
},
|
||||
{
|
||||
id: 'session.scheduleTask.instructions',
|
||||
title: 'Scheduled Task Instructions',
|
||||
group: 'Session',
|
||||
description: 'Hidden instructions attached to the /schedule-task command. Guides the dialogue that defines a scheduled task and optionally creates it through the openchamber tool.',
|
||||
template: `The user wants to set up a scheduled task: a saved prompt that OpenChamber runs automatically on a schedule (daily, weekly, one time, or cron) in a chosen project, with a chosen model and optional Goal Mode.
|
||||
|
||||
Run this as a guided dialogue, not a one-shot answer.
|
||||
|
||||
Use the \`question\` tool only for clarifying decisions that have a small set of concrete answer options you already know from the conversation or your investigation — choices like option A/B/C, scope boundaries, or edge-case behavior. Ask open-ended questions, including what the user wants in the first place, in plain assistant text. Never invent speculative options just to fit the question tool.
|
||||
|
||||
1. Start from the user's intent. If the visible message includes an initial idea, use it immediately. Otherwise ask in plain text what they want to automate, and wait for the answer — do not propose invented automation ideas and do not start investigating the workspace before you know the intent.
|
||||
|
||||
2. Investigate before asking. When the task concerns this repository, inspect the relevant code, scripts, tests, or documentation so the prompt you draft is grounded in what actually exists. Do not ask for information the workspace can answer.
|
||||
|
||||
3. Resolve the task definition:
|
||||
- Name: a short, recognizable task name.
|
||||
- Prompt: the exact instruction the scheduled agent receives on every run. It must be fully self-contained — the scheduled session has no memory of this conversation, so include every path, command, and expectation it needs.
|
||||
- Schedule: a daily time, weekly days plus a time, a one-time date plus a time, or a cron expression; include the timezone when it matters.
|
||||
- Model in provider/model format. Mention agent, variant, or Goal Mode with a token budget only if the user brings them up.
|
||||
|
||||
4. Ask only necessary questions, in batches of at most 3. Prefer concrete, decision-oriented questions.
|
||||
|
||||
5. Do not perform the task's work in this session. The deliverable is the scheduled task definition.
|
||||
|
||||
6. When everything is settled, present the final task definition clearly. If the \`openchamber\` tool is available, offer to create the task yourself and, only after the user explicitly confirms, create it and report the result. If the tool is unavailable, present the definition so the user can add it in OpenChamber's scheduled tasks UI.
|
||||
|
||||
Respond in the same language the user uses.`,
|
||||
},
|
||||
@@ -843,7 +886,7 @@ Respond in the same language the user uses.`,
|
||||
description: 'Hidden instructions attached to the /debug command. Runs a guided root-cause investigation before proposing a fix.',
|
||||
template: `The user wants help debugging an issue. Drive this as a focused root-cause investigation — not a plan, and not an immediate fix.
|
||||
|
||||
Whenever you ask the user a question, use the \`question\` tool. Do not ask questions in plain assistant text.
|
||||
Use the \`question\` tool only for clarifying decisions that have a small set of concrete answer options you already know from the conversation or your investigation — choices like option A/B/C, scope boundaries, or edge-case behavior. Ask open-ended questions, including what the user wants in the first place, in plain assistant text. Never invent speculative options just to fit the question tool.
|
||||
|
||||
1. Get the symptom. When the user describes the problem, capture exactly what is observed versus expected — error messages, stack traces, failing behavior, and when it started. If a key detail is missing to even begin, ask for it briefly.
|
||||
|
||||
@@ -873,7 +916,7 @@ Respond in the same language the user uses.`,
|
||||
description: 'Hidden instructions attached to the /weigh command. Investigates the code, then compares distinct approaches with trade-offs and a recommendation — no plan, no code.',
|
||||
template: `The user knows WHAT they want to do but not HOW to approach it. Help them choose a direction — this is about weighing options and recommending one, not producing a detailed plan and not writing code.
|
||||
|
||||
Whenever you ask the user a question, use the \`question\` tool. Do not ask questions in plain assistant text.
|
||||
Use the \`question\` tool only for clarifying decisions that have a small set of concrete answer options you already know from the conversation or your investigation — choices like option A/B/C, scope boundaries, or edge-case behavior. Ask open-ended questions, including what the user wants in the first place, in plain assistant text. Never invent speculative options just to fit the question tool.
|
||||
|
||||
First, investigate. Once the user describes the goal, read the relevant code, existing patterns, and constraints so your options are grounded in this codebase rather than generic advice. Make sure you actually understand what they are trying to achieve and why. Ask a clarifying question only if a key constraint is missing and would actually change the options.
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test';
|
||||
|
||||
mock.module('./runtime-url', () => ({
|
||||
getRuntimeUrlResolver: () => ({ sse: (path: string) => `http://runtime.test${path}` }),
|
||||
}));
|
||||
|
||||
mock.module('./runtime-switch', () => ({
|
||||
subscribeRuntimeEndpointChanged: () => () => undefined,
|
||||
}));
|
||||
|
||||
class MockEventSource {
|
||||
static CLOSED = 2;
|
||||
static instances: MockEventSource[] = [];
|
||||
|
||||
readyState = 1;
|
||||
onopen: (() => void) | null = null;
|
||||
onmessage: ((event: { data: string }) => void) | null = null;
|
||||
onerror: (() => void) | null = null;
|
||||
|
||||
constructor(public readonly url: string) {
|
||||
MockEventSource.instances.push(this);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.readyState = MockEventSource.CLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
describe('openchamber events', () => {
|
||||
beforeEach(() => {
|
||||
MockEventSource.instances = [];
|
||||
globalThis.window = {} as Window & typeof globalThis;
|
||||
globalThis.EventSource = MockEventSource as unknown as typeof EventSource;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete (globalThis as { window?: unknown }).window;
|
||||
delete (globalThis as { EventSource?: unknown }).EventSource;
|
||||
});
|
||||
|
||||
test('dispatches externally created session events', async () => {
|
||||
const { subscribeOpenchamberEvents } = await import('./openchamberEvents');
|
||||
const events: unknown[] = [];
|
||||
const listener = (event: unknown) => events.push(event);
|
||||
const unsubscribe = subscribeOpenchamberEvents(listener);
|
||||
const source = MockEventSource.instances[0];
|
||||
|
||||
source.onmessage?.({
|
||||
data: JSON.stringify({
|
||||
type: 'openchamber:session-created',
|
||||
properties: {
|
||||
sessionId: 'ses_123',
|
||||
directory: '/repo/worktrees/research',
|
||||
projectId: 'project_1',
|
||||
createdAt: 123,
|
||||
promptDispatched: true,
|
||||
dispatchedAsCommand: false,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(events).toEqual([
|
||||
{
|
||||
type: 'session-created',
|
||||
sessionId: 'ses_123',
|
||||
directory: '/repo/worktrees/research',
|
||||
projectId: 'project_1',
|
||||
createdAt: 123,
|
||||
promptDispatched: true,
|
||||
dispatchedAsCommand: false,
|
||||
},
|
||||
]);
|
||||
unsubscribe();
|
||||
});
|
||||
});
|
||||
@@ -10,7 +10,17 @@ type ScheduledTaskRanEvent = {
|
||||
sessionId?: string;
|
||||
};
|
||||
|
||||
type OpenChamberEvent = ScheduledTaskRanEvent;
|
||||
type SessionCreatedEvent = {
|
||||
type: 'session-created';
|
||||
sessionId: string;
|
||||
directory: string;
|
||||
projectId?: string;
|
||||
createdAt: number;
|
||||
promptDispatched: boolean;
|
||||
dispatchedAsCommand: boolean;
|
||||
};
|
||||
|
||||
type OpenChamberEvent = ScheduledTaskRanEvent | SessionCreatedEvent;
|
||||
type Listener = (event: OpenChamberEvent) => void;
|
||||
|
||||
let eventSource: EventSource | null = null;
|
||||
@@ -80,6 +90,13 @@ const parseEnvelope = (raw: string): { type: string; properties: unknown } | nul
|
||||
}
|
||||
};
|
||||
|
||||
const getEventProperties = (properties: unknown): Record<string, unknown> | null => {
|
||||
if (!properties || typeof properties !== 'object') {
|
||||
return null;
|
||||
}
|
||||
return properties as Record<string, unknown>;
|
||||
};
|
||||
|
||||
const dispatchFromEnvelope = (envelope: { type: string; properties: unknown }) => {
|
||||
if (envelope.type === 'openchamber:event-stream-ready') {
|
||||
reconnectAttempt = 0;
|
||||
@@ -90,17 +107,40 @@ const dispatchFromEnvelope = (envelope: { type: string; properties: unknown }) =
|
||||
return;
|
||||
}
|
||||
|
||||
if (envelope.type === 'openchamber:session-created') {
|
||||
const properties = getEventProperties(envelope.properties);
|
||||
const sessionId = typeof properties?.sessionId === 'string' ? properties.sessionId : '';
|
||||
const directory = typeof properties?.directory === 'string' ? properties.directory : '';
|
||||
if (!sessionId || !directory) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextEvent: SessionCreatedEvent = {
|
||||
type: 'session-created',
|
||||
sessionId,
|
||||
directory,
|
||||
createdAt: typeof properties?.createdAt === 'number' ? properties.createdAt : Date.now(),
|
||||
promptDispatched: properties?.promptDispatched === true,
|
||||
dispatchedAsCommand: properties?.dispatchedAsCommand === true,
|
||||
...(typeof properties?.projectId === 'string' && properties.projectId.length > 0
|
||||
? { projectId: properties.projectId }
|
||||
: {}),
|
||||
};
|
||||
for (const listener of listeners) {
|
||||
listener(nextEvent);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (envelope.type !== 'openchamber:scheduled-task-ran') {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsed = envelope.properties && typeof envelope.properties === 'object'
|
||||
? envelope.properties as Record<string, unknown>
|
||||
: null;
|
||||
const projectId = typeof parsed?.projectId === 'string' ? parsed.projectId : '';
|
||||
const taskId = typeof parsed?.taskId === 'string' ? parsed.taskId : '';
|
||||
const ranAt = typeof parsed?.ranAt === 'number' ? parsed.ranAt : Date.now();
|
||||
const rawStatus = parsed?.status;
|
||||
const properties = getEventProperties(envelope.properties);
|
||||
const projectId = typeof properties?.projectId === 'string' ? properties.projectId : '';
|
||||
const taskId = typeof properties?.taskId === 'string' ? properties.taskId : '';
|
||||
const ranAt = typeof properties?.ranAt === 'number' ? properties.ranAt : Date.now();
|
||||
const rawStatus = properties?.status;
|
||||
const status = rawStatus === 'running' || rawStatus === 'error' ? rawStatus : 'success';
|
||||
if (!projectId || !taskId) {
|
||||
return;
|
||||
@@ -112,7 +152,9 @@ const dispatchFromEnvelope = (envelope: { type: string; properties: unknown }) =
|
||||
taskId,
|
||||
ranAt,
|
||||
status,
|
||||
...(typeof parsed?.sessionId === 'string' && parsed.sessionId.length > 0 ? { sessionId: parsed.sessionId } : {}),
|
||||
...(typeof properties?.sessionId === 'string' && properties.sessionId.length > 0
|
||||
? { sessionId: properties.sessionId }
|
||||
: {}),
|
||||
};
|
||||
for (const listener of listeners) {
|
||||
listener(nextEvent);
|
||||
|
||||
@@ -295,7 +295,7 @@ describe('updateDesktopSettings', () => {
|
||||
|
||||
switchRuntimeEndpoint({ apiBaseUrl: 'https://load-b.example', runtimeKey: 'load-b' });
|
||||
registerSettingsApi(async () => ({}), async () => ({
|
||||
settings: { terminalShell: 'fish', draftStartersCraftGoalAdded: true },
|
||||
settings: { terminalShell: 'fish', draftStartersCraftGoalAdded: true, draftStartersScheduleTaskAdded: true },
|
||||
source: 'web',
|
||||
}));
|
||||
await syncDesktopSettings();
|
||||
@@ -303,14 +303,14 @@ describe('updateDesktopSettings', () => {
|
||||
|
||||
switchRuntimeEndpoint({ apiBaseUrl: 'https://load-a.example', runtimeKey: 'load-a' });
|
||||
registerSettingsApi(async () => ({}), async () => ({
|
||||
settings: { terminalShell: 'bash', draftStartersCraftGoalAdded: true },
|
||||
settings: { terminalShell: 'bash', draftStartersCraftGoalAdded: true, draftStartersScheduleTaskAdded: true },
|
||||
source: 'web',
|
||||
}));
|
||||
await syncDesktopSettings();
|
||||
expect(useUIStore.getState().terminalShell).toBe('bash');
|
||||
|
||||
originalLoad.resolve({
|
||||
settings: { terminalShell: 'zsh', draftStartersCraftGoalAdded: true },
|
||||
settings: { terminalShell: 'zsh', draftStartersCraftGoalAdded: true, draftStartersScheduleTaskAdded: true },
|
||||
source: 'web',
|
||||
});
|
||||
await firstSync;
|
||||
@@ -326,7 +326,7 @@ describe('updateDesktopSettings', () => {
|
||||
themeId: 'theme-a',
|
||||
directoryShowHidden: true,
|
||||
sttModel: 'model-a',
|
||||
draftStartersCraftGoalAdded: true,
|
||||
draftStartersCraftGoalAdded: true, draftStartersScheduleTaskAdded: true,
|
||||
},
|
||||
source: 'web',
|
||||
}));
|
||||
@@ -334,7 +334,7 @@ describe('updateDesktopSettings', () => {
|
||||
|
||||
switchRuntimeEndpoint({ apiBaseUrl: 'https://mirror-b.example', runtimeKey: 'mirror-b' });
|
||||
registerSettingsApi(async () => ({}), async () => ({
|
||||
settings: { draftStartersCraftGoalAdded: true },
|
||||
settings: { draftStartersCraftGoalAdded: true, draftStartersScheduleTaskAdded: true },
|
||||
source: 'web',
|
||||
}));
|
||||
await syncDesktopSettings();
|
||||
@@ -360,7 +360,7 @@ describe('updateDesktopSettings', () => {
|
||||
favoriteModels: [{ providerID: 'anthropic', modelID: 'claude-sonnet-4' }],
|
||||
followUpBehavior: 'steer',
|
||||
draftStarters: [{ type: 'command', name: 'runtime-a' }],
|
||||
draftStartersCraftGoalAdded: true,
|
||||
draftStartersCraftGoalAdded: true, draftStartersScheduleTaskAdded: true,
|
||||
},
|
||||
source: 'web',
|
||||
}));
|
||||
@@ -374,7 +374,7 @@ describe('updateDesktopSettings', () => {
|
||||
|
||||
switchRuntimeEndpoint({ apiBaseUrl: 'https://preferences-b.example', runtimeKey: 'preferences-b' });
|
||||
registerSettingsApi(async () => ({}), async () => ({
|
||||
settings: { draftStartersCraftGoalAdded: true },
|
||||
settings: { draftStartersCraftGoalAdded: true, draftStartersScheduleTaskAdded: true },
|
||||
source: 'web',
|
||||
}));
|
||||
await syncDesktopSettings();
|
||||
@@ -408,7 +408,7 @@ describe('updateDesktopSettings', () => {
|
||||
recentModels: [{ providerID: 'google', modelID: 'gemini-pro' }],
|
||||
recentAgents: ['build', 'plan'],
|
||||
recentEfforts: { 'anthropic/claude-haiku-4': ['high', 'default'] },
|
||||
draftStartersCraftGoalAdded: true,
|
||||
draftStartersCraftGoalAdded: true, draftStartersScheduleTaskAdded: true,
|
||||
} satisfies SettingsPayload;
|
||||
registerSettingsApi(async () => ({}), async () => ({ settings, source: 'web' }));
|
||||
|
||||
@@ -463,7 +463,7 @@ describe('updateDesktopSettings', () => {
|
||||
|
||||
expect(saveCalls).toHaveLength(1);
|
||||
expect(saveCalls[0]).toEqual({
|
||||
draftStartersCraftGoalAdded: true,
|
||||
draftStartersCraftGoalAdded: true, draftStartersScheduleTaskAdded: true,
|
||||
favoriteModels: [{ providerID: 'anthropic', modelID: 'claude-haiku-4' }],
|
||||
hiddenModels: [{ providerID: 'openai', modelID: 'gpt-5' }],
|
||||
collapsedModelProviders: ['openai'],
|
||||
|
||||
@@ -548,6 +548,7 @@ const materializeAuthoritativeUiSettings = (settings: DesktopSettings): DesktopS
|
||||
maxLastMessageLength: defaults.maxLastMessageLength,
|
||||
inputSpellcheckEnabled: defaults.inputSpellcheckEnabled,
|
||||
showOpenCodeUpdateNotifications: defaults.showOpenCodeUpdateNotifications,
|
||||
agentControlToolEnabled: defaults.agentControlToolEnabled,
|
||||
showToolFileIcons: defaults.showToolFileIcons,
|
||||
codeBlockLineWrap: defaults.codeBlockLineWrap,
|
||||
showTurnChangedFiles: defaults.showTurnChangedFiles,
|
||||
@@ -703,6 +704,12 @@ const applyDesktopUiPreferences = (settings: DesktopSettings) => {
|
||||
) {
|
||||
store.setShowOpenCodeUpdateNotifications(settings.showOpenCodeUpdateNotifications);
|
||||
}
|
||||
if (
|
||||
typeof settings.agentControlToolEnabled === 'boolean'
|
||||
&& settings.agentControlToolEnabled !== store.agentControlToolEnabled
|
||||
) {
|
||||
store.setAgentControlToolEnabled(settings.agentControlToolEnabled);
|
||||
}
|
||||
if (typeof settings.showToolFileIcons === 'boolean' && settings.showToolFileIcons !== store.showToolFileIcons) {
|
||||
store.setShowToolFileIcons(settings.showToolFileIcons);
|
||||
}
|
||||
@@ -804,17 +811,33 @@ const applyDesktopUiPreferences = (settings: DesktopSettings) => {
|
||||
...nextStarters.slice(insertAt),
|
||||
];
|
||||
}
|
||||
if (settings.draftStartersScheduleTaskAdded !== true && !nextStarters.some((starter) => starter.type === 'command' && starter.name === 'schedule-task')) {
|
||||
const goalIndex = nextStarters.findIndex((starter) => starter.type === 'command' && starter.name === 'craft-goal');
|
||||
const insertAt = goalIndex >= 0 ? goalIndex + 1 : nextStarters.length;
|
||||
nextStarters = [
|
||||
...nextStarters.slice(0, insertAt),
|
||||
{ type: 'command', name: 'schedule-task' },
|
||||
...nextStarters.slice(insertAt),
|
||||
];
|
||||
}
|
||||
if (JSON.stringify(store.globalDraftStarters) !== JSON.stringify(nextStarters)) {
|
||||
store.setGlobalDraftStarters(nextStarters);
|
||||
}
|
||||
if (settings.draftStartersCraftGoalAdded !== true) {
|
||||
if (settings.draftStartersCraftGoalAdded !== true || settings.draftStartersScheduleTaskAdded !== true) {
|
||||
settings.draftStarters = nextStarters;
|
||||
settings.draftStartersCraftGoalAdded = true;
|
||||
settings.draftStartersScheduleTaskAdded = true;
|
||||
}
|
||||
} else {
|
||||
// The built-in default already contains Craft a Goal and Schedule a Task;
|
||||
// only persist the markers so removing them later remains a durable user
|
||||
// choice.
|
||||
if (settings.draftStartersCraftGoalAdded !== true) {
|
||||
settings.draftStartersCraftGoalAdded = true;
|
||||
}
|
||||
if (settings.draftStartersScheduleTaskAdded !== true) {
|
||||
settings.draftStartersScheduleTaskAdded = true;
|
||||
}
|
||||
} else if (settings.draftStartersCraftGoalAdded !== true) {
|
||||
// The built-in default already contains Craft a Goal; only persist the marker
|
||||
// so removing it later remains a durable user choice.
|
||||
settings.draftStartersCraftGoalAdded = true;
|
||||
}
|
||||
if (typeof settings.terminalFontSize === 'number' && Number.isFinite(settings.terminalFontSize) && settings.terminalFontSize !== store.terminalFontSize) {
|
||||
store.setTerminalFontSize(settings.terminalFontSize);
|
||||
@@ -1023,6 +1046,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
|
||||
if (typeof candidate.draftStartersCraftGoalAdded === 'boolean') {
|
||||
result.draftStartersCraftGoalAdded = candidate.draftStartersCraftGoalAdded;
|
||||
}
|
||||
if (typeof candidate.draftStartersScheduleTaskAdded === 'boolean') {
|
||||
result.draftStartersScheduleTaskAdded = candidate.draftStartersScheduleTaskAdded;
|
||||
}
|
||||
if (typeof candidate.showReasoningTraces === 'boolean') {
|
||||
result.showReasoningTraces = candidate.showReasoningTraces;
|
||||
}
|
||||
@@ -1302,6 +1328,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
|
||||
if (typeof candidate.showOpenCodeUpdateNotifications === 'boolean') {
|
||||
result.showOpenCodeUpdateNotifications = candidate.showOpenCodeUpdateNotifications;
|
||||
}
|
||||
if (typeof candidate.agentControlToolEnabled === 'boolean') {
|
||||
result.agentControlToolEnabled = candidate.agentControlToolEnabled;
|
||||
}
|
||||
if (typeof candidate.openCodeUpdateToastDismissedVersion === 'string') {
|
||||
result.openCodeUpdateToastDismissedVersion = candidate.openCodeUpdateToastDismissedVersion.trim().slice(0, 128);
|
||||
}
|
||||
@@ -1672,7 +1701,8 @@ export const syncDesktopSettings = async (): Promise<void> => {
|
||||
// prevent server settings from reaching the Zustand store.
|
||||
const applySettings = async (settings: DesktopSettings) => {
|
||||
if (!isSettingsRuntimeContextCurrent(context)) return;
|
||||
const shouldPersistCraftGoalMigration = settings.draftStartersCraftGoalAdded !== true;
|
||||
const shouldPersistCraftGoalMigration = settings.draftStartersCraftGoalAdded !== true
|
||||
|| settings.draftStartersScheduleTaskAdded !== true;
|
||||
const authoritativeSettings = materializeAuthoritativeUiSettings(settings);
|
||||
try {
|
||||
persistToLocalStorage(settings);
|
||||
@@ -1693,6 +1723,7 @@ export const syncDesktopSettings = async (): Promise<void> => {
|
||||
await updateDesktopSettings({
|
||||
...(authoritativeSettings.draftStarters ? { draftStarters: authoritativeSettings.draftStarters } : {}),
|
||||
draftStartersCraftGoalAdded: true,
|
||||
draftStartersScheduleTaskAdded: true,
|
||||
});
|
||||
if (!isSettingsRuntimeContextCurrent(context)) return;
|
||||
}
|
||||
|
||||
@@ -452,6 +452,14 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [
|
||||
keywords: ['opencode', 'cli', 'updates'],
|
||||
isAvailable: (ctx) => !ctx.isVSCode,
|
||||
},
|
||||
{
|
||||
id: 'sessions.agent-control-tool',
|
||||
page: 'general',
|
||||
titleKey: 'settings.openchamber.opencodeCli.field.agentControlTool',
|
||||
descriptionKey: 'settings.openchamber.opencodeCli.field.agentControlToolInfo',
|
||||
keywords: ['agent', 'tool', 'orchestration', 'openchamber', 'sessions', 'schedule', 'control'],
|
||||
isAvailable: (ctx) => !ctx.isVSCode,
|
||||
},
|
||||
{
|
||||
id: 'git.github-account',
|
||||
page: 'git',
|
||||
|
||||
@@ -101,7 +101,7 @@ const TOOL_METADATA: Record<string, ToolMetadata> = {
|
||||
]
|
||||
},
|
||||
|
||||
task: {
|
||||
task: {
|
||||
displayName: 'Agent Task',
|
||||
category: 'ai',
|
||||
outputLanguage: 'markdown',
|
||||
@@ -187,6 +187,13 @@ const TOOL_METADATA: Record<string, ToolMetadata> = {
|
||||
]
|
||||
},
|
||||
|
||||
openchamber: {
|
||||
displayName: 'OpenChamber',
|
||||
category: 'system',
|
||||
outputLanguage: 'json',
|
||||
inputFields: []
|
||||
},
|
||||
|
||||
plan_enter: {
|
||||
displayName: 'Plan Mode',
|
||||
category: 'ai',
|
||||
|
||||
Reference in New Issue
Block a user