@namzu/cli 29.0.0 → 29.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +146 -0
- package/README.md +20 -0
- package/dist/browser/commands.d.ts +29 -0
- package/dist/browser/commands.d.ts.map +1 -0
- package/dist/browser/commands.js +361 -0
- package/dist/browser/commands.js.map +1 -0
- package/dist/browser/control.d.ts +79 -0
- package/dist/browser/control.d.ts.map +1 -0
- package/dist/browser/control.js +140 -0
- package/dist/browser/control.js.map +1 -0
- package/dist/browser/describe.d.ts +12 -0
- package/dist/browser/describe.d.ts.map +1 -0
- package/dist/browser/describe.js +27 -0
- package/dist/browser/describe.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +29 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/acp.d.ts.map +1 -1
- package/dist/commands/acp.js +1 -0
- package/dist/commands/acp.js.map +1 -1
- package/dist/commands/browser.d.ts +7 -0
- package/dist/commands/browser.d.ts.map +1 -0
- package/dist/commands/browser.js +16 -0
- package/dist/commands/browser.js.map +1 -0
- package/dist/commands/drain.d.ts +2 -1
- package/dist/commands/drain.d.ts.map +1 -1
- package/dist/commands/drain.js +6 -3
- package/dist/commands/drain.js.map +1 -1
- package/dist/commands/exec-flags.d.ts +2 -1
- package/dist/commands/exec-flags.d.ts.map +1 -1
- package/dist/commands/exec-flags.js +6 -3
- package/dist/commands/exec-flags.js.map +1 -1
- package/dist/commands/exec-json.d.ts.map +1 -1
- package/dist/commands/exec-json.js +2 -1
- package/dist/commands/exec-json.js.map +1 -1
- package/dist/commands/exec.d.ts.map +1 -1
- package/dist/commands/exec.js +2 -1
- package/dist/commands/exec.js.map +1 -1
- package/dist/commands/host-queries.d.ts.map +1 -1
- package/dist/commands/host-queries.js +10 -3
- package/dist/commands/host-queries.js.map +1 -1
- package/dist/commands/schedule.d.ts +9 -0
- package/dist/commands/schedule.d.ts.map +1 -0
- package/dist/commands/schedule.js +126 -0
- package/dist/commands/schedule.js.map +1 -0
- package/dist/commands/serve.d.ts +3 -1
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +5 -3
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/skills.d.ts +10 -0
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +23 -7
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/upgrade.d.ts +5 -0
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +11 -0
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/config/load.d.ts +39 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +232 -1
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +78 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/config/user-config.d.ts +23 -0
- package/dist/config/user-config.d.ts.map +1 -0
- package/dist/config/user-config.js +48 -0
- package/dist/config/user-config.js.map +1 -0
- package/dist/context/capabilities.d.ts +1 -1
- package/dist/context/capabilities.d.ts.map +1 -1
- package/dist/context/capabilities.js +1 -0
- package/dist/context/capabilities.js.map +1 -1
- package/dist/doctor/checks/browser.d.ts +15 -0
- package/dist/doctor/checks/browser.d.ts.map +1 -0
- package/dist/doctor/checks/browser.js +63 -0
- package/dist/doctor/checks/browser.js.map +1 -0
- package/dist/doctor/checks/index.d.ts +4 -1
- package/dist/doctor/checks/index.d.ts.map +1 -1
- package/dist/doctor/checks/index.js +8 -1
- package/dist/doctor/checks/index.js.map +1 -1
- package/dist/doctor/checks/scheduler.d.ts +10 -0
- package/dist/doctor/checks/scheduler.d.ts.map +1 -0
- package/dist/doctor/checks/scheduler.js +74 -0
- package/dist/doctor/checks/scheduler.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/notifications/desktop/freedesktop.d.ts +10 -0
- package/dist/integrations/notifications/desktop/freedesktop.d.ts.map +1 -0
- package/dist/integrations/notifications/desktop/freedesktop.js +33 -0
- package/dist/integrations/notifications/desktop/freedesktop.js.map +1 -0
- package/dist/integrations/notifications/desktop/macos.d.ts +6 -0
- package/dist/integrations/notifications/desktop/macos.d.ts.map +1 -0
- package/dist/integrations/notifications/desktop/macos.js +17 -0
- package/dist/integrations/notifications/desktop/macos.js.map +1 -0
- package/dist/integrations/notifications/desktop/sanitize.d.ts +16 -0
- package/dist/integrations/notifications/desktop/sanitize.d.ts.map +1 -0
- package/dist/integrations/notifications/desktop/sanitize.js +49 -0
- package/dist/integrations/notifications/desktop/sanitize.js.map +1 -0
- package/dist/integrations/notifications/desktop/throttle.d.ts +10 -0
- package/dist/integrations/notifications/desktop/throttle.d.ts.map +1 -0
- package/dist/integrations/notifications/desktop/throttle.js +39 -0
- package/dist/integrations/notifications/desktop/throttle.js.map +1 -0
- package/dist/integrations/notifications/desktop/windows-toast.d.ts +23 -0
- package/dist/integrations/notifications/desktop/windows-toast.d.ts.map +1 -0
- package/dist/integrations/notifications/desktop/windows-toast.js +56 -0
- package/dist/integrations/notifications/desktop/windows-toast.js.map +1 -0
- package/dist/integrations/notifications/desktop.d.ts +82 -0
- package/dist/integrations/notifications/desktop.d.ts.map +1 -0
- package/dist/integrations/notifications/desktop.js +198 -0
- package/dist/integrations/notifications/desktop.js.map +1 -0
- package/dist/integrations/resident/session-step.d.ts.map +1 -1
- package/dist/integrations/resident/session-step.js +1 -0
- package/dist/integrations/resident/session-step.js.map +1 -1
- package/dist/integrations/subagents/runtime.d.ts +6 -0
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +14 -5
- package/dist/integrations/subagents/runtime.js.map +1 -1
- package/dist/permissions/browser-sites.d.ts +95 -0
- package/dist/permissions/browser-sites.d.ts.map +1 -0
- package/dist/permissions/browser-sites.js +270 -0
- package/dist/permissions/browser-sites.js.map +1 -0
- package/dist/permissions/checks.d.ts.map +1 -1
- package/dist/permissions/checks.js +7 -1
- package/dist/permissions/checks.js.map +1 -1
- package/dist/permissions/rules.d.ts.map +1 -1
- package/dist/permissions/rules.js +4 -8
- package/dist/permissions/rules.js.map +1 -1
- package/dist/schedule/build.d.ts +79 -0
- package/dist/schedule/build.d.ts.map +1 -0
- package/dist/schedule/build.js +246 -0
- package/dist/schedule/build.js.map +1 -0
- package/dist/schedule/changes.d.ts +22 -0
- package/dist/schedule/changes.d.ts.map +1 -0
- package/dist/schedule/changes.js +55 -0
- package/dist/schedule/changes.js.map +1 -0
- package/dist/schedule/commands/add.d.ts +24 -0
- package/dist/schedule/commands/add.d.ts.map +1 -0
- package/dist/schedule/commands/add.js +459 -0
- package/dist/schedule/commands/add.js.map +1 -0
- package/dist/schedule/commands/args.d.ts +22 -0
- package/dist/schedule/commands/args.d.ts.map +1 -0
- package/dist/schedule/commands/args.js +80 -0
- package/dist/schedule/commands/args.js.map +1 -0
- package/dist/schedule/commands/confirm-prompt.d.ts +6 -0
- package/dist/schedule/commands/confirm-prompt.d.ts.map +1 -0
- package/dist/schedule/commands/confirm-prompt.js +21 -0
- package/dist/schedule/commands/confirm-prompt.js.map +1 -0
- package/dist/schedule/commands/lifecycle.d.ts +39 -0
- package/dist/schedule/commands/lifecycle.d.ts.map +1 -0
- package/dist/schedule/commands/lifecycle.js +585 -0
- package/dist/schedule/commands/lifecycle.js.map +1 -0
- package/dist/schedule/commands/list.d.ts +33 -0
- package/dist/schedule/commands/list.d.ts.map +1 -0
- package/dist/schedule/commands/list.js +202 -0
- package/dist/schedule/commands/list.js.map +1 -0
- package/dist/schedule/commands/logs.d.ts +7 -0
- package/dist/schedule/commands/logs.d.ts.map +1 -0
- package/dist/schedule/commands/logs.js +106 -0
- package/dist/schedule/commands/logs.js.map +1 -0
- package/dist/schedule/commands/service.d.ts +16 -0
- package/dist/schedule/commands/service.d.ts.map +1 -0
- package/dist/schedule/commands/service.js +458 -0
- package/dist/schedule/commands/service.js.map +1 -0
- package/dist/schedule/daemon/daemon.d.ts +156 -0
- package/dist/schedule/daemon/daemon.d.ts.map +1 -0
- package/dist/schedule/daemon/daemon.js +1248 -0
- package/dist/schedule/daemon/daemon.js.map +1 -0
- package/dist/schedule/daemon/endpoint.d.ts +38 -0
- package/dist/schedule/daemon/endpoint.d.ts.map +1 -0
- package/dist/schedule/daemon/endpoint.js +158 -0
- package/dist/schedule/daemon/endpoint.js.map +1 -0
- package/dist/schedule/daemon/exit.d.ts +8 -0
- package/dist/schedule/daemon/exit.d.ts.map +1 -0
- package/dist/schedule/daemon/exit.js +8 -0
- package/dist/schedule/daemon/exit.js.map +1 -0
- package/dist/schedule/daemon/log.d.ts +12 -0
- package/dist/schedule/daemon/log.d.ts.map +1 -0
- package/dist/schedule/daemon/log.js +53 -0
- package/dist/schedule/daemon/log.js.map +1 -0
- package/dist/schedule/daemon/notify.d.ts +31 -0
- package/dist/schedule/daemon/notify.d.ts.map +1 -0
- package/dist/schedule/daemon/notify.js +89 -0
- package/dist/schedule/daemon/notify.js.map +1 -0
- package/dist/schedule/daemon/retention.d.ts +16 -0
- package/dist/schedule/daemon/retention.d.ts.map +1 -0
- package/dist/schedule/daemon/retention.js +46 -0
- package/dist/schedule/daemon/retention.js.map +1 -0
- package/dist/schedule/daemon/sessions.d.ts +23 -0
- package/dist/schedule/daemon/sessions.d.ts.map +1 -0
- package/dist/schedule/daemon/sessions.js +52 -0
- package/dist/schedule/daemon/sessions.js.map +1 -0
- package/dist/schedule/env.d.ts +28 -0
- package/dist/schedule/env.d.ts.map +1 -0
- package/dist/schedule/env.js +99 -0
- package/dist/schedule/env.js.map +1 -0
- package/dist/schedule/fire/browser-preflight.d.ts +41 -0
- package/dist/schedule/fire/browser-preflight.d.ts.map +1 -0
- package/dist/schedule/fire/browser-preflight.js +73 -0
- package/dist/schedule/fire/browser-preflight.js.map +1 -0
- package/dist/schedule/fire/fire.d.ts +78 -0
- package/dist/schedule/fire/fire.d.ts.map +1 -0
- package/dist/schedule/fire/fire.js +482 -0
- package/dist/schedule/fire/fire.js.map +1 -0
- package/dist/schedule/fire/result.d.ts +13 -0
- package/dist/schedule/fire/result.d.ts.map +1 -0
- package/dist/schedule/fire/result.js +18 -0
- package/dist/schedule/fire/result.js.map +1 -0
- package/dist/schedule/fire/unattended-note.d.ts +22 -0
- package/dist/schedule/fire/unattended-note.d.ts.map +1 -0
- package/dist/schedule/fire/unattended-note.js +46 -0
- package/dist/schedule/fire/unattended-note.js.map +1 -0
- package/dist/schedule/floor.d.ts +116 -0
- package/dist/schedule/floor.d.ts.map +1 -0
- package/dist/schedule/floor.js +1051 -0
- package/dist/schedule/floor.js.map +1 -0
- package/dist/schedule/folder.d.ts +34 -0
- package/dist/schedule/folder.d.ts.map +1 -0
- package/dist/schedule/folder.js +84 -0
- package/dist/schedule/folder.js.map +1 -0
- package/dist/schedule/paths.d.ts +49 -0
- package/dist/schedule/paths.d.ts.map +1 -0
- package/dist/schedule/paths.js +53 -0
- package/dist/schedule/paths.js.map +1 -0
- package/dist/schedule/policy.d.ts +141 -0
- package/dist/schedule/policy.d.ts.map +1 -0
- package/dist/schedule/policy.js +378 -0
- package/dist/schedule/policy.js.map +1 -0
- package/dist/schedule/resume-command.d.ts +36 -0
- package/dist/schedule/resume-command.d.ts.map +1 -0
- package/dist/schedule/resume-command.js +72 -0
- package/dist/schedule/resume-command.js.map +1 -0
- package/dist/schedule/service/detect.d.ts +22 -0
- package/dist/schedule/service/detect.d.ts.map +1 -0
- package/dist/schedule/service/detect.js +29 -0
- package/dist/schedule/service/detect.js.map +1 -0
- package/dist/schedule/service/index.d.ts +48 -0
- package/dist/schedule/service/index.d.ts.map +1 -0
- package/dist/schedule/service/index.js +352 -0
- package/dist/schedule/service/index.js.map +1 -0
- package/dist/schedule/service/launchd.d.ts +24 -0
- package/dist/schedule/service/launchd.d.ts.map +1 -0
- package/dist/schedule/service/launchd.js +84 -0
- package/dist/schedule/service/launchd.js.map +1 -0
- package/dist/schedule/service/manifest.d.ts +44 -0
- package/dist/schedule/service/manifest.d.ts.map +1 -0
- package/dist/schedule/service/manifest.js +16 -0
- package/dist/schedule/service/manifest.js.map +1 -0
- package/dist/schedule/service/names.d.ts +9 -0
- package/dist/schedule/service/names.d.ts.map +1 -0
- package/dist/schedule/service/names.js +22 -0
- package/dist/schedule/service/names.js.map +1 -0
- package/dist/schedule/service/quote.d.ts +24 -0
- package/dist/schedule/service/quote.d.ts.map +1 -0
- package/dist/schedule/service/quote.js +64 -0
- package/dist/schedule/service/quote.js.map +1 -0
- package/dist/schedule/service/runner.d.ts +17 -0
- package/dist/schedule/service/runner.d.ts.map +1 -0
- package/dist/schedule/service/runner.js +31 -0
- package/dist/schedule/service/runner.js.map +1 -0
- package/dist/schedule/service/systemd.d.ts +37 -0
- package/dist/schedule/service/systemd.d.ts.map +1 -0
- package/dist/schedule/service/systemd.js +96 -0
- package/dist/schedule/service/systemd.js.map +1 -0
- package/dist/schedule/service/windows-task.d.ts +51 -0
- package/dist/schedule/service/windows-task.d.ts.map +1 -0
- package/dist/schedule/service/windows-task.js +134 -0
- package/dist/schedule/service/windows-task.js.map +1 -0
- package/dist/schedule/service/wsl.d.ts +57 -0
- package/dist/schedule/service/wsl.d.ts.map +1 -0
- package/dist/schedule/service/wsl.js +111 -0
- package/dist/schedule/service/wsl.js.map +1 -0
- package/dist/schedule/store/atomic.d.ts +43 -0
- package/dist/schedule/store/atomic.d.ts.map +1 -0
- package/dist/schedule/store/atomic.js +135 -0
- package/dist/schedule/store/atomic.js.map +1 -0
- package/dist/schedule/store/claims.d.ts +26 -0
- package/dist/schedule/store/claims.d.ts.map +1 -0
- package/dist/schedule/store/claims.js +57 -0
- package/dist/schedule/store/claims.js.map +1 -0
- package/dist/schedule/store/digest.d.ts +18 -0
- package/dist/schedule/store/digest.d.ts.map +1 -0
- package/dist/schedule/store/digest.js +139 -0
- package/dist/schedule/store/digest.js.map +1 -0
- package/dist/schedule/store/history.d.ts +21 -0
- package/dist/schedule/store/history.d.ts.map +1 -0
- package/dist/schedule/store/history.js +114 -0
- package/dist/schedule/store/history.js.map +1 -0
- package/dist/schedule/store/jobs.d.ts +61 -0
- package/dist/schedule/store/jobs.d.ts.map +1 -0
- package/dist/schedule/store/jobs.js +210 -0
- package/dist/schedule/store/jobs.js.map +1 -0
- package/dist/schedule/store/state.d.ts +19 -0
- package/dist/schedule/store/state.d.ts.map +1 -0
- package/dist/schedule/store/state.js +38 -0
- package/dist/schedule/store/state.js.map +1 -0
- package/dist/schedule/types.d.ts +256 -0
- package/dist/schedule/types.d.ts.map +1 -0
- package/dist/schedule/types.js +9 -0
- package/dist/schedule/types.js.map +1 -0
- package/dist/skills/catalog.d.ts +96 -0
- package/dist/skills/catalog.d.ts.map +1 -0
- package/dist/skills/catalog.js +226 -0
- package/dist/skills/catalog.js.map +1 -0
- package/dist/skills/save.d.ts +148 -0
- package/dist/skills/save.d.ts.map +1 -0
- package/dist/skills/save.js +344 -0
- package/dist/skills/save.js.map +1 -0
- package/dist/skills/store.d.ts +103 -16
- package/dist/skills/store.d.ts.map +1 -1
- package/dist/skills/store.js +230 -29
- package/dist/skills/store.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +591 -72
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/LiveActivity.d.ts +8 -1
- package/dist/tui/LiveActivity.d.ts.map +1 -1
- package/dist/tui/LiveActivity.js +20 -4
- package/dist/tui/LiveActivity.js.map +1 -1
- package/dist/tui/PermissionOverlay.d.ts +24 -4
- package/dist/tui/PermissionOverlay.d.ts.map +1 -1
- package/dist/tui/PermissionOverlay.js +113 -15
- package/dist/tui/PermissionOverlay.js.map +1 -1
- package/dist/tui/SaveSkillOverlay.d.ts +33 -0
- package/dist/tui/SaveSkillOverlay.d.ts.map +1 -0
- package/dist/tui/SaveSkillOverlay.js +118 -0
- package/dist/tui/SaveSkillOverlay.js.map +1 -0
- package/dist/tui/agent.d.ts +185 -5
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +325 -54
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/browser-notices.d.ts +33 -0
- package/dist/tui/browser-notices.d.ts.map +1 -0
- package/dist/tui/browser-notices.js +102 -0
- package/dist/tui/browser-notices.js.map +1 -0
- package/dist/tui/permission-review.d.ts.map +1 -1
- package/dist/tui/permission-review.js +53 -4
- package/dist/tui/permission-review.js.map +1 -1
- package/dist/tui/schedule/host-commands.d.ts +34 -0
- package/dist/tui/schedule/host-commands.d.ts.map +1 -0
- package/dist/tui/schedule/host-commands.js +309 -0
- package/dist/tui/schedule/host-commands.js.map +1 -0
- package/dist/tui/schedule/integration.d.ts +62 -0
- package/dist/tui/schedule/integration.d.ts.map +1 -0
- package/dist/tui/schedule/integration.js +145 -0
- package/dist/tui/schedule/integration.js.map +1 -0
- package/dist/tui/schedule/loop-host.d.ts +42 -0
- package/dist/tui/schedule/loop-host.d.ts.map +1 -0
- package/dist/tui/schedule/loop-host.js +153 -0
- package/dist/tui/schedule/loop-host.js.map +1 -0
- package/dist/tui/schedule/resume.d.ts +122 -0
- package/dist/tui/schedule/resume.d.ts.map +1 -0
- package/dist/tui/schedule/resume.js +324 -0
- package/dist/tui/schedule/resume.js.map +1 -0
- package/dist/tui/schedule/startup.d.ts +11 -0
- package/dist/tui/schedule/startup.d.ts.map +1 -0
- package/dist/tui/schedule/startup.js +121 -0
- package/dist/tui/schedule/startup.js.map +1 -0
- package/dist/tui/schedule/tool-host.d.ts +44 -0
- package/dist/tui/schedule/tool-host.d.ts.map +1 -0
- package/dist/tui/schedule/tool-host.js +294 -0
- package/dist/tui/schedule/tool-host.js.map +1 -0
- package/dist/tui/skills/learning.d.ts +74 -0
- package/dist/tui/skills/learning.d.ts.map +1 -0
- package/dist/tui/skills/learning.js +131 -0
- package/dist/tui/skills/learning.js.map +1 -0
- package/dist/tui/skills/suggest-setting.d.ts +22 -0
- package/dist/tui/skills/suggest-setting.d.ts.map +1 -0
- package/dist/tui/skills/suggest-setting.js +55 -0
- package/dist/tui/skills/suggest-setting.js.map +1 -0
- package/dist/tui/skills/suggestion-ledger.d.ts +35 -0
- package/dist/tui/skills/suggestion-ledger.d.ts.map +1 -0
- package/dist/tui/skills/suggestion-ledger.js +52 -0
- package/dist/tui/skills/suggestion-ledger.js.map +1 -0
- package/dist/tui/slashCommands.d.ts +20 -0
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +76 -2
- package/dist/tui/slashCommands.js.map +1 -1
- package/dist/tui/turn-interruption.d.ts +20 -1
- package/dist/tui/turn-interruption.d.ts.map +1 -1
- package/dist/tui/turn-interruption.js +43 -0
- package/dist/tui/turn-interruption.js.map +1 -1
- package/dist/tui/types.d.ts +9 -1
- package/dist/tui/types.d.ts.map +1 -1
- package/package.json +5 -3
- package/skills/README.md +15 -0
- package/skills/browser-automation/SKILL.md +84 -0
- package/skills/schedule-task/SKILL.md +140 -0
- package/skills/skill-creator/SKILL.md +101 -0
package/dist/tui/App.js
CHANGED
|
@@ -16,8 +16,8 @@ import { TURN_LIMIT_FIELDS, turnLimitCommands } from './turn-limits-settings.js'
|
|
|
16
16
|
import { discoverProviders } from '../integrations/providers/discover.js';
|
|
17
17
|
import { ProviderSetup } from './ProviderSetup.js';
|
|
18
18
|
import { ToolOutputViewer } from './ToolOutputViewer.js';
|
|
19
|
-
import { relative } from 'node:path';
|
|
20
|
-
import { DEFAULT_MAX_GOAL_ROUNDS, DiskMessageFeedbackStore, GoalRoundLimitError, HostCommandRegistry, SessionGoalActivation, StaleGoalError, TurnCancelled, asSessionId, createAssistantMessage, createUserMessage, generateSessionId, generateTurnId, isCompactionMessage, kernelHostCommands, } from '@namzu/sdk';
|
|
19
|
+
import { join, relative } from 'node:path';
|
|
20
|
+
import { DEFAULT_MAX_GOAL_ROUNDS, DiskMessageFeedbackStore, GoalRoundLimitError, HostCommandRegistry, SessionGoalActivation, StaleGoalError, TurnCancelled, asSessionId, createAssistantMessage, createUserMessage, generateSessionId, generateTurnId, isCompactionMessage, kernelHostCommands, SKILL_TOOL_NAME, } from '@namzu/sdk';
|
|
21
21
|
import { Box, Text, useApp, useInput, useStdout, useWindowSize } from 'ink';
|
|
22
22
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
23
23
|
import { pinTrustedProjectPath, resolveTrustedProjectContext, } from '../config/trusted-project-context.js';
|
|
@@ -35,7 +35,7 @@ import { checkUpdates } from '../integrations/updates.js';
|
|
|
35
35
|
import { renderMemoryReport, renderMemorySaveResult, renderStoredMemorySection, renderTypedNoteResult, } from '../memory/presentation.js';
|
|
36
36
|
import { appendMemoryWithStatus, readMemory } from '../memory/store.js';
|
|
37
37
|
import { effectivePermissionMode, permissionModeDescription, permissionModeLabel, } from '../permissions/mode.js';
|
|
38
|
-
import { composeSkillsPrompt, discoverSkills, loadSkillBody } from '../skills/store.js';
|
|
38
|
+
import { NO_SKILLS_FOUND, composeSkillsPrompt, discoverSkills, loadSkillBody, renderSkillRoster, skillTierLabel, } from '../skills/store.js';
|
|
39
39
|
import { discoverUserCommands } from '../user-commands/store.js';
|
|
40
40
|
import { AgentCockpit, AgentNarrationBand, AgentTaskPanel, AgentTranscript, activeSubagentCohorts, agentCockpitIsWide, agentPhasePageSize, agentPhases, agentPickerPageSize, agentTranscriptPageSize, agentWorkflowPageSize, agentWorkflows, maxAgentTranscriptTailOffset, } from './AgentExplorer.js';
|
|
41
41
|
import { completionRow, launchReceipt, settleLine } from './agent-transcript-rows.js';
|
|
@@ -49,9 +49,16 @@ import { CopyPicker } from './CopyPicker.js';
|
|
|
49
49
|
import { EditPromptPicker } from './EditPromptPicker.js';
|
|
50
50
|
import { LiveActivity, formatElapsed } from './LiveActivity.js';
|
|
51
51
|
import { webCallTitle, webLiveStatus, webSettledLine } from './web-activity.js';
|
|
52
|
-
import { PermissionOverlay } from './PermissionOverlay.js';
|
|
52
|
+
import { PermissionOverlay, permissionAnswers } from './PermissionOverlay.js';
|
|
53
53
|
import { Picker } from './Picker.js';
|
|
54
54
|
import { ResumePicker } from './ResumePicker.js';
|
|
55
|
+
import { resolveNamzuHome } from '../integrations/state/home.js';
|
|
56
|
+
import { createScheduleIntegration } from './schedule/integration.js';
|
|
57
|
+
import { SaveSkillOverlay } from './SaveSkillOverlay.js';
|
|
58
|
+
import { SKILL_CREATOR_SKILL, buildSaveSkillTool, learnSkillPrompt, newSkillPrompt, } from '../skills/save.js';
|
|
59
|
+
import { SUGGESTIONS_STOPPED_NOTICE, createTurnActivity, judgeSkillSuggestion, observeTurnEvent, skillSuggestionNotice, } from './skills/learning.js';
|
|
60
|
+
import { FRESH_LEDGER, admitSuggestion, readSuggestionLedger, writeSuggestionLedger, } from './skills/suggestion-ledger.js';
|
|
61
|
+
import { setSkillSuggestions } from './skills/suggest-setting.js';
|
|
55
62
|
import { StatusBar } from './StatusBar.js';
|
|
56
63
|
import { isRepeatedNotice } from './notices.js';
|
|
57
64
|
import { checklistProgress } from './Checklist.js';
|
|
@@ -81,7 +88,8 @@ import { renderCheckpoints, renderRestore } from '../checkpoints/store.js';
|
|
|
81
88
|
import { expandFileMentions, listMentionableFiles } from './mentions.js';
|
|
82
89
|
import { openInBrowser } from './open-browser.js';
|
|
83
90
|
import { buildPermissionReview, buildPermissionSummary, permissionReviewPageRows, permissionReviewRefusal, permissionReviewRows, releasedByApproveAll, } from './permission-review.js';
|
|
84
|
-
import { describeTurnInterruption, describeTurnStop } from './turn-interruption.js';
|
|
91
|
+
import { PAUSED_TURN_LINES, describeTurnInterruption, describeTurnStop, handoffContinuationNote, } from './turn-interruption.js';
|
|
92
|
+
import { browserSiteNotes, describeBrowserHandoff, runBrowserSlash } from './browser-notices.js';
|
|
85
93
|
import { moveSelection } from './selection-window.js';
|
|
86
94
|
import { describeShellEscape, describeShellEscapeForModel, runShellEscape, shellEscapeCommand, } from './shell-escape.js';
|
|
87
95
|
import { baseBranchReviewPrompt, commitReviewPrompt, hostCommandNames, kernelCommandDescriptors, mergeHostCommands, renderAgents, renderOutcome, reviewPrompt, runSlash, } from './slashCommands.js';
|
|
@@ -314,6 +322,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
314
322
|
const initialConversationIdRef = useRef(initialCtx.initialConversationId);
|
|
315
323
|
const [history, setHistory] = useState([]);
|
|
316
324
|
const [state, setState] = useState('idle');
|
|
325
|
+
/** For the scheduler integration's loop timer, which reads it outside render. */
|
|
326
|
+
const stateRef = useRef(state);
|
|
327
|
+
stateRef.current = state;
|
|
317
328
|
const [phase, setPhase] = useState('probing');
|
|
318
329
|
const [session, setSession] = useState(null);
|
|
319
330
|
// Once a usable session publishes the transcript, temporary lifecycle
|
|
@@ -617,6 +628,25 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
617
628
|
}, []);
|
|
618
629
|
const [composerDraft, setComposerDraft] = useState(null);
|
|
619
630
|
const [composerHasDraft, setComposerHasDraft] = useState(false);
|
|
631
|
+
// A turn a tool paused for a person (`ToolResult.handoff`), while its
|
|
632
|
+
// notice is the newest thing on screen: Enter continues it, Esc stops it.
|
|
633
|
+
// The ref is what the key handler reads; the state is what the footer shows.
|
|
634
|
+
/**
|
|
635
|
+
* The stream of the turn running now, so a tool's own screen can close the
|
|
636
|
+
* reply it streamed before the tool ran. The kernel hands the tool's start
|
|
637
|
+
* to this loop only when its batch settles, so a reply streamed with a
|
|
638
|
+
* tool call stays pending while that tool asks the operator something; a
|
|
639
|
+
* row written then (the schedule tool's confirmation) cannot reach
|
|
640
|
+
* scrollback past the pending reply, and the top of a tall one was cut
|
|
641
|
+
* off while the operator was asked about it.
|
|
642
|
+
*/
|
|
643
|
+
const liveStreamRef = useRef(null);
|
|
644
|
+
const [handoffPark, setHandoffParkState] = useState(null);
|
|
645
|
+
const handoffParkRef = useRef(null);
|
|
646
|
+
const setHandoffPark = useCallback((value) => {
|
|
647
|
+
handoffParkRef.current = value;
|
|
648
|
+
setHandoffParkState(value);
|
|
649
|
+
}, []);
|
|
620
650
|
const composerDraftTokenRef = useRef(0);
|
|
621
651
|
/** Bounded child session projection published by the current AgentSession. */
|
|
622
652
|
const [subagents, setSubagentsState] = useState([]);
|
|
@@ -837,6 +867,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
837
867
|
* server's child process outlives the object that opened it.
|
|
838
868
|
*/
|
|
839
869
|
const previousSessionRef = useRef(null);
|
|
870
|
+
/** The profile `/browser profile` chose, kept across session rebuilds. */
|
|
871
|
+
const browserProfileRef = useRef(undefined);
|
|
872
|
+
/** The current session's browser, for notices written from event handlers. */
|
|
873
|
+
const browserControlRef = useRef(undefined);
|
|
874
|
+
useEffect(() => {
|
|
875
|
+
browserControlRef.current = session?.browser;
|
|
876
|
+
}, [session]);
|
|
840
877
|
useEffect(() => {
|
|
841
878
|
const source = session?.subagents;
|
|
842
879
|
setAgentSurface(null);
|
|
@@ -910,6 +947,89 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
910
947
|
// turns attribute to the resumed conversation.
|
|
911
948
|
const sessionsRef = useRef(null);
|
|
912
949
|
const scopeRef = useRef(null);
|
|
950
|
+
/**
|
|
951
|
+
* Scheduled jobs and session loops (`./schedule/`). Built once; what it
|
|
952
|
+
* reads later — the transcript writer, the question and permission
|
|
953
|
+
* screens, the submit path, the session's model — is filled in each
|
|
954
|
+
* render through `scheduleLiveRef`.
|
|
955
|
+
*/
|
|
956
|
+
const scheduleLiveRef = useRef(null);
|
|
957
|
+
const scheduleRef = useRef(null);
|
|
958
|
+
if (scheduleRef.current === null) {
|
|
959
|
+
scheduleRef.current = createScheduleIntegration({
|
|
960
|
+
home: () => sessionsRef.current?.root ?? resolveNamzuHome(),
|
|
961
|
+
cwd: () => ctxRef.current.cwd,
|
|
962
|
+
extraRoots: () => ctxRef.current.additionalDirectories ?? [],
|
|
963
|
+
config: () => ctxRef.current,
|
|
964
|
+
model: () => scheduleLiveRef.current?.model,
|
|
965
|
+
sessionId: () => (conversationMaterializedRef.current ? scopeRef.current?.sessionId : undefined),
|
|
966
|
+
loopsFile: () => {
|
|
967
|
+
const sessions = sessionsRef.current;
|
|
968
|
+
const scope = scopeRef.current;
|
|
969
|
+
if (!sessions || !scope || !conversationMaterializedRef.current)
|
|
970
|
+
return undefined;
|
|
971
|
+
try {
|
|
972
|
+
return join(sessions.paths.sessionDir({ sessionId: scope.sessionId }), 'loops.json');
|
|
973
|
+
}
|
|
974
|
+
catch {
|
|
975
|
+
return undefined;
|
|
976
|
+
}
|
|
977
|
+
},
|
|
978
|
+
isIdle: () => stateRef.current === 'idle' && abortRef.current === null,
|
|
979
|
+
say: (text) => scheduleLiveRef.current?.say(text),
|
|
980
|
+
ask: (question, signal) => scheduleLiveRef.current?.ask(question, signal) ?? Promise.resolve({ kind: 'skip' }),
|
|
981
|
+
askPermission: (request) => scheduleLiveRef.current?.askPermission(request) ??
|
|
982
|
+
Promise.resolve({ kind: 'reject', feedback: 'Nobody can answer yet.' }),
|
|
983
|
+
submit: (text) => scheduleLiveRef.current?.submit(text),
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* `save_skill`'s confirmation. The tool is the TUI's alone (never exec, a
|
|
988
|
+
* scheduled run or a sub-agent), and its question is this screen, not the
|
|
989
|
+
* permission gate, so no permission mode answers it for the operator.
|
|
990
|
+
*/
|
|
991
|
+
const [saveSkillPrompt, setSaveSkillPromptState] = useState(null);
|
|
992
|
+
const saveSkillPromptRef = useRef(null);
|
|
993
|
+
const setSaveSkillPrompt = useCallback((next) => {
|
|
994
|
+
saveSkillPromptRef.current = next;
|
|
995
|
+
setSaveSkillPromptState(next);
|
|
996
|
+
}, []);
|
|
997
|
+
const saveSkillNotifyRef = useRef(null);
|
|
998
|
+
const saveSkillToolRef = useRef(null);
|
|
999
|
+
if (saveSkillToolRef.current === null) {
|
|
1000
|
+
saveSkillToolRef.current = buildSaveSkillTool({
|
|
1001
|
+
cwd: () => ctxRef.current.cwd,
|
|
1002
|
+
config: () => ctxRef.current.skills,
|
|
1003
|
+
sessionId: () => conversationMaterializedRef.current ? scopeRef.current?.sessionId : undefined,
|
|
1004
|
+
confirm: (request, signal) => new Promise((resolve) => {
|
|
1005
|
+
if (signal?.aborted) {
|
|
1006
|
+
resolve('cancel');
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
const entry = {
|
|
1010
|
+
request,
|
|
1011
|
+
resolve: (answer) => {
|
|
1012
|
+
signal?.removeEventListener('abort', onAbort);
|
|
1013
|
+
// The screen answers from its own key handler, which Ink calls
|
|
1014
|
+
// before App's (a child subscribes first). Clearing the ref now
|
|
1015
|
+
// would hand the same Esc or Ctrl+C to App, which reads it as
|
|
1016
|
+
// an interrupt of the turn this answer is for. Let the key's
|
|
1017
|
+
// dispatch finish first.
|
|
1018
|
+
queueMicrotask(() => {
|
|
1019
|
+
if (saveSkillPromptRef.current === entry)
|
|
1020
|
+
setSaveSkillPrompt(null);
|
|
1021
|
+
});
|
|
1022
|
+
resolve(answer);
|
|
1023
|
+
},
|
|
1024
|
+
};
|
|
1025
|
+
const onAbort = () => entry.resolve('cancel');
|
|
1026
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
1027
|
+
setSaveSkillPrompt(entry);
|
|
1028
|
+
saveSkillNotifyRef.current?.();
|
|
1029
|
+
}),
|
|
1030
|
+
saved: ({ name, path }) => scheduleLiveRef.current?.say(`✎ Saved skill ${name} to ${path}. The model is offered it from the next turn; /skills ${name} activates it now.`),
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
913
1033
|
/**
|
|
914
1034
|
* A provider session can be ready before a person has started a conversation.
|
|
915
1035
|
* The scope still needs a mutable session cursor, but its generated id is
|
|
@@ -948,6 +1068,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
948
1068
|
const [goalDriveVersion, setGoalDriveVersion] = useState(0);
|
|
949
1069
|
const [goalStatus, setGoalStatus] = useState(null);
|
|
950
1070
|
const pendingGoalResumeRef = useRef(null);
|
|
1071
|
+
/**
|
|
1072
|
+
* The conversation `namzu resume <id>` opened, until the screen is ready to
|
|
1073
|
+
* look at it once: a scheduled run parked on a decision there is continued
|
|
1074
|
+
* then, so its permission prompt is on screen without typing `/resume`.
|
|
1075
|
+
*/
|
|
1076
|
+
const pendingScheduledResumeRef = useRef(null);
|
|
951
1077
|
const wakeGoalDriver = useCallback(() => setGoalDriveVersion((version) => version + 1), []);
|
|
952
1078
|
/**
|
|
953
1079
|
* Conversation writes in the order the operator produced them.
|
|
@@ -1160,6 +1286,56 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1160
1286
|
]);
|
|
1161
1287
|
return id;
|
|
1162
1288
|
}, [nextId]);
|
|
1289
|
+
/**
|
|
1290
|
+
* The skill proposal's per-conversation facts: whether this conversation
|
|
1291
|
+
* has proposed one, and whether it has used a skill. Keyed by the
|
|
1292
|
+
* conversation generation, so `/clear`, `/resume` and a fork start over.
|
|
1293
|
+
*/
|
|
1294
|
+
const learningRef = useRef({ generation: -1, proposed: false, skillUsed: false });
|
|
1295
|
+
/** `/skills save off|on` in this session, over whatever the config files say. */
|
|
1296
|
+
const skillSuggestOverrideRef = useRef(undefined);
|
|
1297
|
+
const conversationLearning = useCallback(() => {
|
|
1298
|
+
if (learningRef.current.generation !== conversationGenRef.current) {
|
|
1299
|
+
learningRef.current = {
|
|
1300
|
+
generation: conversationGenRef.current,
|
|
1301
|
+
proposed: false,
|
|
1302
|
+
skillUsed: false,
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
return learningRef.current;
|
|
1306
|
+
}, []);
|
|
1307
|
+
/**
|
|
1308
|
+
* After a turn settles: one dim line proposing to save it as a skill, when
|
|
1309
|
+
* the turn earned it (`judgeSkillSuggestion`) and the anti-nag ledger lets
|
|
1310
|
+
* it through. Costs nothing and saves nothing; `/skills save` does that.
|
|
1311
|
+
*/
|
|
1312
|
+
const offerSkillSuggestion = useCallback((st) => {
|
|
1313
|
+
if (!st.learning)
|
|
1314
|
+
return;
|
|
1315
|
+
const conversation = conversationLearning();
|
|
1316
|
+
const config = ctxRef.current.skills;
|
|
1317
|
+
const verdict = judgeSkillSuggestion(st.learning, {
|
|
1318
|
+
suggest: skillSuggestOverrideRef.current ?? config?.suggest,
|
|
1319
|
+
...(config?.suggestMinToolCalls !== undefined
|
|
1320
|
+
? { minToolCalls: config.suggestMinToolCalls }
|
|
1321
|
+
: {}),
|
|
1322
|
+
planMode: st.startedInPlanMode === true || permissionModeRef.current === 'plan',
|
|
1323
|
+
skillUsedInConversation: conversation.skillUsed,
|
|
1324
|
+
proposedInConversation: conversation.proposed,
|
|
1325
|
+
skillFlowTurn: st.skillFlow === true,
|
|
1326
|
+
});
|
|
1327
|
+
if (!verdict.suggest)
|
|
1328
|
+
return;
|
|
1329
|
+
const home = resolveNamzuHome();
|
|
1330
|
+
const admission = admitSuggestion(readSuggestionLedger(home));
|
|
1331
|
+
if (admission.show === 'nothing')
|
|
1332
|
+
return;
|
|
1333
|
+
conversation.proposed = true;
|
|
1334
|
+
writeSuggestionLedger(home, admission.next);
|
|
1335
|
+
pushMessage('system', admission.show === 'proposal'
|
|
1336
|
+
? skillSuggestionNotice(verdict.steps, verdict.tools)
|
|
1337
|
+
: SUGGESTIONS_STOPPED_NOTICE, false, '✻', undefined, theme.text.muted);
|
|
1338
|
+
}, [conversationLearning, pushMessage]);
|
|
1163
1339
|
/**
|
|
1164
1340
|
* `/agents [running|available|batches]`, answered by the delegation UI's
|
|
1165
1341
|
* `agentsSlashCommand` over this session's live monitor and the batches the
|
|
@@ -1532,7 +1708,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1532
1708
|
})();
|
|
1533
1709
|
}, [pushMessage]);
|
|
1534
1710
|
const activateSkill = useCallback((name) => {
|
|
1535
|
-
const info = discoverSkills({
|
|
1711
|
+
const info = discoverSkills({
|
|
1712
|
+
cwd: ctx.cwd,
|
|
1713
|
+
...(ctx.skills ? { config: ctx.skills } : {}),
|
|
1714
|
+
}).find((skill) => skill.name === name);
|
|
1536
1715
|
if (!info) {
|
|
1537
1716
|
pushMessage('system', `No skill named "${name}". See /skills.`);
|
|
1538
1717
|
return;
|
|
@@ -1543,12 +1722,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1543
1722
|
...previous.filter((skill) => skill.name !== info.name),
|
|
1544
1723
|
{ name: info.name, body },
|
|
1545
1724
|
]);
|
|
1725
|
+
conversationLearning().skillUsed = true;
|
|
1546
1726
|
pushMessage('system', `Activated skill: ${info.name}`);
|
|
1547
1727
|
}
|
|
1548
1728
|
catch (error) {
|
|
1549
1729
|
pushMessage('system', `Could not load skill "${name}": ${error instanceof Error ? error.message : String(error)}`);
|
|
1550
1730
|
}
|
|
1551
|
-
}, [ctx.cwd, pushMessage]);
|
|
1731
|
+
}, [conversationLearning, ctx.cwd, ctx.skills, pushMessage]);
|
|
1552
1732
|
const removeStoredCredential = useCallback((target) => {
|
|
1553
1733
|
const path = credentialsPath();
|
|
1554
1734
|
const hadClaude = readStoredSubscriptionCredential() !== null;
|
|
@@ -2151,6 +2331,23 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2151
2331
|
const finalizeMessage = useCallback((id, finalContent) => {
|
|
2152
2332
|
setMessages((prev) => prev.map((m) => m.id === id ? { ...m, content: finalContent ?? m.content, pending: false } : m));
|
|
2153
2333
|
}, []);
|
|
2334
|
+
/**
|
|
2335
|
+
* Close the reply the running turn streamed before a tool took over, as a
|
|
2336
|
+
* tool's start does. For a tool that shows the operator something of its
|
|
2337
|
+
* own while it runs: the reply is complete by then (tools run after the
|
|
2338
|
+
* model's response ends), and left pending it holds every later row out
|
|
2339
|
+
* of scrollback. Text that arrives later starts a new reply.
|
|
2340
|
+
*/
|
|
2341
|
+
const closeLiveReply = useCallback(() => {
|
|
2342
|
+
const st = liveStreamRef.current;
|
|
2343
|
+
if (!st)
|
|
2344
|
+
return;
|
|
2345
|
+
flushStream(st);
|
|
2346
|
+
if (st.assistantId) {
|
|
2347
|
+
finalizeMessage(st.assistantId);
|
|
2348
|
+
st.assistantId = null;
|
|
2349
|
+
}
|
|
2350
|
+
}, [finalizeMessage, flushStream]);
|
|
2154
2351
|
// Open the SDK session store and select the durable conversation once.
|
|
2155
2352
|
// This is an admission gate for every startup, not optional persistence: a
|
|
2156
2353
|
// corrupt or split estate must not silently widen into cwd-local state and
|
|
@@ -2172,6 +2369,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2172
2369
|
? { text: persistedOutput, provenance: 'persisted' }
|
|
2173
2370
|
: null;
|
|
2174
2371
|
pendingGoalResumeRef.current = requestedConversationId;
|
|
2372
|
+
pendingScheduledResumeRef.current = requestedConversationId;
|
|
2175
2373
|
initialConversationIdRef.current = undefined;
|
|
2176
2374
|
conversationMaterializedRef.current = true;
|
|
2177
2375
|
}
|
|
@@ -2243,21 +2441,50 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2243
2441
|
if (signal?.aborted)
|
|
2244
2442
|
return;
|
|
2245
2443
|
const activeCtx = ctxRef.current;
|
|
2444
|
+
const primary = prefs.providers[0];
|
|
2445
|
+
if (primary && scheduleLiveRef.current) {
|
|
2446
|
+
scheduleLiveRef.current.model = {
|
|
2447
|
+
provider: primary.id,
|
|
2448
|
+
...(primary.model ? { model: primary.model } : {}),
|
|
2449
|
+
};
|
|
2450
|
+
}
|
|
2451
|
+
const browserConfig = activeCtx.browser;
|
|
2452
|
+
const browserProfile = browserProfileRef.current ?? browserConfig?.defaultProfile;
|
|
2453
|
+
const browserOptions = browserConfig
|
|
2454
|
+
? {
|
|
2455
|
+
...(browserProfile ? { profile: browserProfile } : {}),
|
|
2456
|
+
...(browserConfig.engine ? { engine: browserConfig.engine } : {}),
|
|
2457
|
+
...(browserConfig.headless ? { headless: browserConfig.headless } : {}),
|
|
2458
|
+
...(browserConfig.sites ? { sites: browserConfig.sites } : {}),
|
|
2459
|
+
...(browserConfig.keepOpen ? { keepOpen: true } : {}),
|
|
2460
|
+
...(sessionsRef.current ? { home: sessionsRef.current.root } : {}),
|
|
2461
|
+
}
|
|
2462
|
+
: undefined;
|
|
2246
2463
|
const s = await createAgentSession(prefs, detectedNow, {
|
|
2247
2464
|
scope,
|
|
2248
2465
|
cwd: activeCtx.cwd,
|
|
2466
|
+
// A person is here to confirm: the model may propose scheduled jobs
|
|
2467
|
+
// and session loops (never in exec, a scheduled run or a sub-agent).
|
|
2468
|
+
extraTools: [
|
|
2469
|
+
...(scheduleRef.current?.tools() ?? []),
|
|
2470
|
+
...(saveSkillToolRef.current ? [saveSkillToolRef.current] : []),
|
|
2471
|
+
],
|
|
2249
2472
|
...(activeCtx.structuredOutput ? { structuredOutput: activeCtx.structuredOutput } : {}),
|
|
2250
2473
|
...(activeCtx.additionalDirectories
|
|
2251
2474
|
? { additionalDirectories: activeCtx.additionalDirectories }
|
|
2252
2475
|
: {}),
|
|
2253
2476
|
...(sessionsRef.current ? { stateRoot: sessionsRef.current.root } : {}),
|
|
2254
2477
|
enableComputerUse: true,
|
|
2478
|
+
// The browser tools, when the config did not switch them off. The
|
|
2479
|
+
// profile is the one `/browser profile` chose, else the config's.
|
|
2480
|
+
...(browserOptions ? { browser: browserOptions } : {}),
|
|
2255
2481
|
rules: activeCtx.rules,
|
|
2256
2482
|
...(sessionsRef.current
|
|
2257
2483
|
? { sessionGoals: sessionsRef.current.goals, conversationSessions: sessionsRef.current }
|
|
2258
2484
|
: {}),
|
|
2259
2485
|
...(activeCtx.mcpServers ? { mcpServers: activeCtx.mcpServers } : {}),
|
|
2260
2486
|
...(activeCtx.plugins ? { plugins: activeCtx.plugins } : {}),
|
|
2487
|
+
...(activeCtx.skills ? { skills: activeCtx.skills } : {}),
|
|
2261
2488
|
...(activeCtx.web ? { web: activeCtx.web } : {}),
|
|
2262
2489
|
...(activeCtx.hooks ? { hooks: activeCtx.hooks } : {}),
|
|
2263
2490
|
...(activeCtx.compaction ? { compaction: activeCtx.compaction } : {}),
|
|
@@ -2862,7 +3089,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2862
3089
|
return;
|
|
2863
3090
|
}
|
|
2864
3091
|
await session.abandonTurn(active.turnId, reason);
|
|
2865
|
-
pushMessage('system',
|
|
3092
|
+
pushMessage('system', PAUSED_TURN_LINES.abandoned);
|
|
2866
3093
|
}
|
|
2867
3094
|
catch (err) {
|
|
2868
3095
|
pushMessage('system', `Could not abandon the turn: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -2875,7 +3102,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2875
3102
|
*/
|
|
2876
3103
|
// `applyEvent` is declared further down; the resume path reads it at call time.
|
|
2877
3104
|
const applyEventRef = useRef(null);
|
|
2878
|
-
const resumeActiveTurn = useCallback(async () => {
|
|
3105
|
+
const resumeActiveTurn = useCallback(async (resume) => {
|
|
2879
3106
|
const sessions = sessionsRef.current;
|
|
2880
3107
|
const scope = scopeRef.current;
|
|
2881
3108
|
if (!sessions || !scope || !conversationMaterializedRef.current || !session?.hasProvider)
|
|
@@ -2887,6 +3114,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2887
3114
|
pushMessage('system', 'Wait for the running turn to finish before resuming the parked one.');
|
|
2888
3115
|
return true;
|
|
2889
3116
|
}
|
|
3117
|
+
setHandoffPark(null);
|
|
2890
3118
|
const ac = new AbortController();
|
|
2891
3119
|
abortRef.current = ac;
|
|
2892
3120
|
setState('thinking');
|
|
@@ -2900,15 +3128,58 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2900
3128
|
sessionId: scope.sessionId,
|
|
2901
3129
|
notification: null,
|
|
2902
3130
|
};
|
|
2903
|
-
|
|
3131
|
+
liveStreamRef.current = st;
|
|
3132
|
+
let scheduledRun = false;
|
|
3133
|
+
let restoreBrowser;
|
|
2904
3134
|
try {
|
|
3135
|
+
// A scheduled run parked on a decision: the operator answers the
|
|
3136
|
+
// parked batch here, and the turn continues under the job's rules.
|
|
3137
|
+
const scheduled = active.paused
|
|
3138
|
+
? await scheduleRef.current?.prepareResume(permissionModeRef.current, {
|
|
3139
|
+
cwd: ctxRef.current.cwd,
|
|
3140
|
+
roots: session.directories?.list() ?? [],
|
|
3141
|
+
sandboxed: session.sandbox.workspace !== 'host',
|
|
3142
|
+
// The resumed turn runs on the job's provider: one this
|
|
3143
|
+
// session has no credential for is refused before asking.
|
|
3144
|
+
providers: detected.map((item) => item.entry.id),
|
|
3145
|
+
browser: session.browser !== undefined,
|
|
3146
|
+
})
|
|
3147
|
+
: undefined;
|
|
3148
|
+
if (scheduled && 'leave' in scheduled) {
|
|
3149
|
+
pushMessage('system', 'The scheduled run stays waiting. /resume asks again.');
|
|
3150
|
+
return true;
|
|
3151
|
+
}
|
|
3152
|
+
if (scheduled && 'abandon' in scheduled) {
|
|
3153
|
+
scheduledRun = true;
|
|
3154
|
+
if (!session.abandonTurn)
|
|
3155
|
+
throw new Error('this session cannot abandon turns');
|
|
3156
|
+
await session.abandonTurn(active.turnId, scheduled.abandon);
|
|
3157
|
+
pushMessage('system', PAUSED_TURN_LINES.abandonedScheduled);
|
|
3158
|
+
return true;
|
|
3159
|
+
}
|
|
3160
|
+
scheduledRun = scheduled !== undefined;
|
|
3161
|
+
const { browser: jobBrowser, ...scheduledParams } = scheduled ?? {};
|
|
3162
|
+
// A job's browser grant: this turn drives the job's profile, held
|
|
3163
|
+
// to its sites, and the session's own comes back when it ends.
|
|
3164
|
+
if (jobBrowser && session.browser) {
|
|
3165
|
+
restoreBrowser = await session.browser.runAs(jobBrowser);
|
|
3166
|
+
pushMessage('system', `The browser uses the job’s profile ${jobBrowser.profile} for this run, on its sites only.`);
|
|
3167
|
+
}
|
|
3168
|
+
// Said once the turn really continues: a scheduled park can still
|
|
3169
|
+
// be left waiting or abandoned above.
|
|
3170
|
+
pushMessage('system', PAUSED_TURN_LINES.resuming, false, '▶');
|
|
2905
3171
|
for await (const event of session.resumePaused({
|
|
2906
3172
|
turnId: active.turnId,
|
|
2907
3173
|
signal: ac.signal,
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
3174
|
+
...(scheduled ? scheduledParams : {
|
|
3175
|
+
// The operator's mode, read at every decision like a new turn's:
|
|
3176
|
+
// `/resume` in plan mode stays read-only, and so do its children.
|
|
3177
|
+
permissionMode: permissionModeRef.current,
|
|
3178
|
+
currentPermissionMode: () => permissionModeRef.current,
|
|
3179
|
+
...(resume?.handoffReason
|
|
3180
|
+
? { systemNote: handoffContinuationNote(resume.handoffReason) }
|
|
3181
|
+
: {}),
|
|
3182
|
+
}),
|
|
2912
3183
|
})) {
|
|
2913
3184
|
applyEventRef.current?.(event, st);
|
|
2914
3185
|
}
|
|
@@ -2926,9 +3197,38 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2926
3197
|
if (abortRef.current === ac)
|
|
2927
3198
|
abortRef.current = null;
|
|
2928
3199
|
setState('idle');
|
|
3200
|
+
// A scheduled run answered here records its end in its job now —
|
|
3201
|
+
// completed, failed or cancelled — not at a scheduler's next tick,
|
|
3202
|
+
// which may never come. One that parked again stays waiting.
|
|
3203
|
+
if (scheduledRun)
|
|
3204
|
+
await scheduleRef.current?.settleAnswered();
|
|
3205
|
+
await restoreBrowser?.().catch(() => undefined);
|
|
2929
3206
|
}
|
|
2930
3207
|
return true;
|
|
2931
|
-
}, [finalizeMessage, flushStream, pushMessage, session, state]);
|
|
3208
|
+
}, [detected, finalizeMessage, flushStream, pushMessage, session, setHandoffPark, state]);
|
|
3209
|
+
// `namzu resume <id>` of a scheduled run parked on a decision: what the
|
|
3210
|
+
// notification and `/schedule` tell the operator to run. Continue it once
|
|
3211
|
+
// the screen is ready, exactly as `/resume` would, so the parked call is on
|
|
3212
|
+
// the permission screen; the job and session checks in `prepareResume`
|
|
3213
|
+
// still refuse a session that does not run as the job does. Any other
|
|
3214
|
+
// parked or interrupted turn is left for the operator's own `/resume`.
|
|
3215
|
+
useEffect(() => {
|
|
3216
|
+
const requested = pendingScheduledResumeRef.current;
|
|
3217
|
+
if (!requested || phase !== 'ready' || state !== 'idle' || !session?.hasProvider)
|
|
3218
|
+
return;
|
|
3219
|
+
const sessions = sessionsRef.current;
|
|
3220
|
+
if (!sessions || scopeRef.current?.sessionId !== requested || abortRef.current)
|
|
3221
|
+
return;
|
|
3222
|
+
pendingScheduledResumeRef.current = null;
|
|
3223
|
+
void (async () => {
|
|
3224
|
+
const active = await activeConversationTurn(sessions, asSessionId(requested)).catch(() => undefined);
|
|
3225
|
+
if (!active?.paused || !(await scheduleRef.current?.isParked()))
|
|
3226
|
+
return;
|
|
3227
|
+
if (scopeRef.current?.sessionId !== requested)
|
|
3228
|
+
return;
|
|
3229
|
+
await resumeActiveTurn();
|
|
3230
|
+
})();
|
|
3231
|
+
}, [phase, resumeActiveTurn, session, state]);
|
|
2932
3232
|
const doResume = useCallback(async () => {
|
|
2933
3233
|
const sessions = sessionsRef.current ?? (await ensureSessions(), sessionsRef.current);
|
|
2934
3234
|
if (!sessions) {
|
|
@@ -3596,10 +3896,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3596
3896
|
});
|
|
3597
3897
|
}
|
|
3598
3898
|
const summary = buildPermissionSummary(review.text);
|
|
3899
|
+
const siteNotes = browserSiteNotes(req.toolCalls, browserControlRef.current?.status());
|
|
3599
3900
|
return new Promise((resolve) => {
|
|
3600
3901
|
if (permissionResolveRef.current) {
|
|
3601
3902
|
permissionQueueRef.current.push({
|
|
3602
|
-
permission: { ...req, review: review.text, summary },
|
|
3903
|
+
permission: { ...req, review: review.text, summary, siteNotes },
|
|
3603
3904
|
resolve,
|
|
3604
3905
|
});
|
|
3605
3906
|
setQueuedPermissionCount(permissionQueueRef.current.length);
|
|
@@ -3609,7 +3910,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3609
3910
|
permissionOpenedAtRef.current = Date.now();
|
|
3610
3911
|
setPermissionReviewOffset(0);
|
|
3611
3912
|
setPermissionDetailsOpen(!summary.complete);
|
|
3612
|
-
setPermission({ ...req, review: review.text, summary });
|
|
3913
|
+
setPermission({ ...req, review: review.text, summary, siteNotes });
|
|
3613
3914
|
setState('awaiting-permission');
|
|
3614
3915
|
sendTerminalNotification({ kind: 'approval-required' });
|
|
3615
3916
|
});
|
|
@@ -3620,15 +3921,26 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3620
3921
|
* and the tool's promise waits on the row the operator picks. One question
|
|
3621
3922
|
* at a time: the tool is not concurrency-safe, so a second cannot arrive
|
|
3622
3923
|
* while the first is up.
|
|
3924
|
+
*
|
|
3925
|
+
* `signal`, when the caller has one (the `schedule` tool's confirmation,
|
|
3926
|
+
* which waits on a person and so is given the tool's own abort signal),
|
|
3927
|
+
* is honoured the way `save_skill`'s screen honours the same signal: an
|
|
3928
|
+
* abort — the tool's own deadline elapsing, or the turn stopping —
|
|
3929
|
+
* answers as `abort` and takes the picker off screen, rather than
|
|
3930
|
+
* leaving it live after the tool has already given up on the answer.
|
|
3623
3931
|
*/
|
|
3624
|
-
const askQuestion = useCallback((question) => new Promise((resolve) => {
|
|
3932
|
+
const askQuestion = useCallback((question, signal) => new Promise((resolve) => {
|
|
3933
|
+
if (signal?.aborted) {
|
|
3934
|
+
resolve({ kind: 'abort' });
|
|
3935
|
+
return;
|
|
3936
|
+
}
|
|
3937
|
+
closeLiveReply();
|
|
3625
3938
|
questionRef.current = { question, resolve };
|
|
3626
3939
|
const values = [
|
|
3627
3940
|
...question.options.map((option) => option.id),
|
|
3628
3941
|
...(question.allowFreeText ? [FREE_TEXT_ANSWER] : []),
|
|
3629
3942
|
];
|
|
3630
|
-
|
|
3631
|
-
setChoicePicker({
|
|
3943
|
+
const picker = {
|
|
3632
3944
|
kind: 'user-question',
|
|
3633
3945
|
title: question.question,
|
|
3634
3946
|
notice: question.header
|
|
@@ -3644,9 +3956,17 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3644
3956
|
? [{ label: 'Something else…', description: 'Answer in your own words' }]
|
|
3645
3957
|
: []),
|
|
3646
3958
|
],
|
|
3647
|
-
}
|
|
3959
|
+
};
|
|
3960
|
+
const onAbort = () => {
|
|
3961
|
+
if (choicePickerRef.current === picker)
|
|
3962
|
+
setChoicePicker(null);
|
|
3963
|
+
resolveQuestion({ kind: 'abort' });
|
|
3964
|
+
};
|
|
3965
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
3966
|
+
setSelectedChoice(0);
|
|
3967
|
+
setChoicePicker(picker);
|
|
3648
3968
|
sendTerminalNotification({ kind: 'approval-required' });
|
|
3649
|
-
}), [sendTerminalNotification, setChoicePicker, setSelectedChoice]);
|
|
3969
|
+
}), [closeLiveReply, sendTerminalNotification, setChoicePicker, setSelectedChoice, resolveQuestion]);
|
|
3650
3970
|
/**
|
|
3651
3971
|
* Grow this turn's open task block, or open one. The whole plan rides along
|
|
3652
3972
|
* so the block shows the checklist as it stood after the operation; a
|
|
@@ -3670,6 +3990,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3670
3990
|
// `st` carries the streaming-assistant bubble id + accumulated text across
|
|
3671
3991
|
// events within a turn/stream.
|
|
3672
3992
|
const applyEvent = useCallback((event, st) => {
|
|
3993
|
+
if (st.learning)
|
|
3994
|
+
observeTurnEvent(st.learning, event);
|
|
3995
|
+
if (event.kind === 'tool-start' && event.toolName === SKILL_TOOL_NAME) {
|
|
3996
|
+
conversationLearning().skillUsed = true;
|
|
3997
|
+
}
|
|
3673
3998
|
const ensureAssistant = () => {
|
|
3674
3999
|
if (!st.assistantId)
|
|
3675
4000
|
st.assistantId = pushMessage('assistant', '', true);
|
|
@@ -3935,6 +4260,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3935
4260
|
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
3936
4261
|
break;
|
|
3937
4262
|
}
|
|
4263
|
+
if (event.cancelled) {
|
|
4264
|
+
// A No on the tool's own screen: the person's answer, not a failure.
|
|
4265
|
+
pushMessage('tool', done?.label ?? formatToolCall(event.toolName, event.summary), false, '○', undefined, theme.text.muted, event.durationMs !== undefined ? formatElapsed(event.durationMs) : undefined);
|
|
4266
|
+
pushMessage('tool', event.resultLabel ?? 'Cancelled', false, '⎿');
|
|
4267
|
+
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
4268
|
+
break;
|
|
4269
|
+
}
|
|
3938
4270
|
const catalogue = !event.isError && event.toolName === 'agent_models' && event.output !== undefined
|
|
3939
4271
|
? modelCatalogueView(event.output) : undefined;
|
|
3940
4272
|
if (catalogue !== undefined) {
|
|
@@ -4091,6 +4423,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4091
4423
|
if (closing)
|
|
4092
4424
|
pushMessage('system', closing, false, '✻', undefined, theme.text.muted);
|
|
4093
4425
|
}
|
|
4426
|
+
offerSkillSuggestion(st);
|
|
4094
4427
|
break;
|
|
4095
4428
|
}
|
|
4096
4429
|
case 'paused':
|
|
@@ -4102,7 +4435,22 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4102
4435
|
st.outcome = 'stopped';
|
|
4103
4436
|
st.queuePauseOutcome = 'paused';
|
|
4104
4437
|
st.notification = { kind: 'turn-settled', outcome: 'stopped' };
|
|
4105
|
-
|
|
4438
|
+
if (event.handoff) {
|
|
4439
|
+
// Nothing failed and nothing needs approving: a tool needs the
|
|
4440
|
+
// operator to do something first. Continuing is one key.
|
|
4441
|
+
const browser = browserControlRef.current;
|
|
4442
|
+
const browserStatus = browser?.status();
|
|
4443
|
+
const browserNotice = describeBrowserHandoff(event.handoff, browserStatus);
|
|
4444
|
+
// Without a window the sign-in happens in `namzu browser
|
|
4445
|
+
// login`, which needs the profile this session holds.
|
|
4446
|
+
if (browserNotice && browser && browserStatus?.headless)
|
|
4447
|
+
void browser.release();
|
|
4448
|
+
pushMessage('system', browserNotice ??
|
|
4449
|
+
`${describeTurnInterruption(event)}\nWhen that is done, press Enter to continue · Esc to stop.`, false, '‖');
|
|
4450
|
+
setHandoffPark({ turnId: event.turnId, reason: event.handoff.reason });
|
|
4451
|
+
}
|
|
4452
|
+
else
|
|
4453
|
+
pushMessage('system', describeTurnInterruption(event), false, '‖');
|
|
4106
4454
|
break;
|
|
4107
4455
|
case 'error':
|
|
4108
4456
|
closeAssistant();
|
|
@@ -4115,7 +4463,16 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4115
4463
|
st.notification = null;
|
|
4116
4464
|
break;
|
|
4117
4465
|
}
|
|
4118
|
-
}, [
|
|
4466
|
+
}, [
|
|
4467
|
+
appendToMessage,
|
|
4468
|
+
conversationLearning,
|
|
4469
|
+
finalizeMessage,
|
|
4470
|
+
flushStream,
|
|
4471
|
+
offerSkillSuggestion,
|
|
4472
|
+
pushMessage,
|
|
4473
|
+
setHandoffPark,
|
|
4474
|
+
writeTaskBlock,
|
|
4475
|
+
]);
|
|
4119
4476
|
applyEventRef.current = applyEvent;
|
|
4120
4477
|
const runTurn = useCallback(async (prompt) => {
|
|
4121
4478
|
// A passive queue effect can still hold the previous render's session
|
|
@@ -4243,7 +4600,19 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4243
4600
|
outcome: null,
|
|
4244
4601
|
sessionId: destination,
|
|
4245
4602
|
notification: null,
|
|
4603
|
+
...(prompt.kind === 'human'
|
|
4604
|
+
? {
|
|
4605
|
+
learning: createTurnActivity(),
|
|
4606
|
+
...(prompt.skillFlow ? { skillFlow: true } : {}),
|
|
4607
|
+
startedInPlanMode: permissionModeRef.current === 'plan',
|
|
4608
|
+
}
|
|
4609
|
+
: {}),
|
|
4246
4610
|
};
|
|
4611
|
+
liveStreamRef.current = st;
|
|
4612
|
+
// A skill activated with /skills <name> stays in the prompt of every
|
|
4613
|
+
// later conversation too; a turn run under one already used a skill.
|
|
4614
|
+
if (st.learning && activeSkills.length > 0)
|
|
4615
|
+
st.learning.skillUsed = true;
|
|
4247
4616
|
const ac = new AbortController();
|
|
4248
4617
|
const turnToken = {};
|
|
4249
4618
|
let abnormalTerminal;
|
|
@@ -4819,6 +5188,8 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4819
5188
|
pushMessage('system', `Conversation history is being ${operation}. Wait for it to finish before sending another command or prompt.`);
|
|
4820
5189
|
return;
|
|
4821
5190
|
}
|
|
5191
|
+
// The operator moved on; a parked turn is still there for /resume.
|
|
5192
|
+
setHandoffPark(null);
|
|
4822
5193
|
setHistory((prev) => [...prev, value]);
|
|
4823
5194
|
const selectionIntent = !attachments?.length ? parseModelSelectionIntent(value) : undefined;
|
|
4824
5195
|
if (selectionIntent) {
|
|
@@ -4859,6 +5230,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4859
5230
|
// including the queue — so a command-driven turn is not a second way
|
|
4860
5231
|
// to run one.
|
|
4861
5232
|
let outgoing = value;
|
|
5233
|
+
let skillFlow = false;
|
|
4862
5234
|
const slash = runSlash(value, slashCtx, hostCommands);
|
|
4863
5235
|
if (slash) {
|
|
4864
5236
|
switch (slash.kind) {
|
|
@@ -5330,20 +5702,16 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5330
5702
|
// distinction the headless commands get from `--cwd`. They are
|
|
5331
5703
|
// the same value today, and were the same value in `exec --json`
|
|
5332
5704
|
// too until they were not.
|
|
5333
|
-
const skills = discoverSkills({
|
|
5705
|
+
const skills = discoverSkills({
|
|
5706
|
+
cwd: ctx.cwd,
|
|
5707
|
+
...(ctx.skills ? { config: ctx.skills } : {}),
|
|
5708
|
+
});
|
|
5334
5709
|
if (skills.length === 0) {
|
|
5335
|
-
pushMessage('system',
|
|
5710
|
+
pushMessage('system', NO_SKILLS_FOUND);
|
|
5336
5711
|
return;
|
|
5337
5712
|
}
|
|
5338
5713
|
const activeNames = new Set(activeSkills.map((s) => s.name));
|
|
5339
|
-
|
|
5340
|
-
// A refused skill is shown with its reason rather than hidden.
|
|
5341
|
-
// Dropping it silently would leave someone wondering where a
|
|
5342
|
-
// file they can see on disk went.
|
|
5343
|
-
s.problem
|
|
5344
|
-
? `! ${s.name} — ${s.problem}`
|
|
5345
|
-
: `${activeNames.has(s.name) ? '● ' : '○ '}${s.name} — ${s.description}`);
|
|
5346
|
-
pushMessage('system', `Skills (● active):\n ${lines.join('\n ')}`);
|
|
5714
|
+
pushMessage('system', renderSkillRoster(skills, activeNames));
|
|
5347
5715
|
return;
|
|
5348
5716
|
}
|
|
5349
5717
|
case 'plugins': {
|
|
@@ -5372,9 +5740,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5372
5740
|
return;
|
|
5373
5741
|
}
|
|
5374
5742
|
case 'skill-picker': {
|
|
5375
|
-
const skills = discoverSkills({
|
|
5743
|
+
const skills = discoverSkills({
|
|
5744
|
+
cwd: ctx.cwd,
|
|
5745
|
+
...(ctx.skills ? { config: ctx.skills } : {}),
|
|
5746
|
+
});
|
|
5376
5747
|
if (skills.length === 0) {
|
|
5377
|
-
pushMessage('system',
|
|
5748
|
+
pushMessage('system', NO_SKILLS_FOUND);
|
|
5378
5749
|
return;
|
|
5379
5750
|
}
|
|
5380
5751
|
const activeNames = new Set(activeSkills.map((skill) => skill.name));
|
|
@@ -5387,7 +5758,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5387
5758
|
label: skill.name,
|
|
5388
5759
|
description: skill.problem
|
|
5389
5760
|
? `Unavailable: ${skill.problem}`
|
|
5390
|
-
: `${skill.description} · ${skill.source}`,
|
|
5761
|
+
: `${skill.description} · ${skill.source} (${skillTierLabel(skill.tier)})`,
|
|
5391
5762
|
current: activeNames.has(skill.name),
|
|
5392
5763
|
})),
|
|
5393
5764
|
});
|
|
@@ -5397,6 +5768,46 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5397
5768
|
activateSkill(slash.name);
|
|
5398
5769
|
return;
|
|
5399
5770
|
}
|
|
5771
|
+
case 'new-skill':
|
|
5772
|
+
case 'save-skill': {
|
|
5773
|
+
const creator = discoverSkills({
|
|
5774
|
+
cwd: ctx.cwd,
|
|
5775
|
+
...(ctx.skills ? { config: ctx.skills } : {}),
|
|
5776
|
+
}).find((skill) => skill.name === SKILL_CREATOR_SKILL);
|
|
5777
|
+
if (!creator || creator.problem || creator.invocation === 'operator') {
|
|
5778
|
+
pushMessage('system', `The ${SKILL_CREATOR_SKILL} skill is not available to the model here${creator?.problem ? ` (${creator.problem})` : ''}. Turn the built-in skills back on (skills.builtin) or write a SKILL.md by hand under ~/.namzu/skills/<name>/.`);
|
|
5779
|
+
return;
|
|
5780
|
+
}
|
|
5781
|
+
// Falls through to the queue-or-send a typed message takes, as
|
|
5782
|
+
// `prompt` does below.
|
|
5783
|
+
skillFlow = true;
|
|
5784
|
+
if (slash.kind === 'new-skill')
|
|
5785
|
+
outgoing = newSkillPrompt(slash.idea);
|
|
5786
|
+
else {
|
|
5787
|
+
outgoing = learnSkillPrompt(slash.name);
|
|
5788
|
+
// The proposal was taken up: this conversation proposes no
|
|
5789
|
+
// more, and the ignored-in-a-row count starts again.
|
|
5790
|
+
conversationLearning().proposed = true;
|
|
5791
|
+
const home = resolveNamzuHome();
|
|
5792
|
+
const ledger = readSuggestionLedger(home);
|
|
5793
|
+
if (ledger.unanswered > 0)
|
|
5794
|
+
writeSuggestionLedger(home, { ...ledger, unanswered: 0 });
|
|
5795
|
+
}
|
|
5796
|
+
break;
|
|
5797
|
+
}
|
|
5798
|
+
case 'skill-suggestions': {
|
|
5799
|
+
// The session follows the choice whatever the files say; the
|
|
5800
|
+
// message says whether a later start will too.
|
|
5801
|
+
skillSuggestOverrideRef.current = slash.on;
|
|
5802
|
+
if (slash.on)
|
|
5803
|
+
writeSuggestionLedger(resolveNamzuHome(), FRESH_LEDGER);
|
|
5804
|
+
const profile = ctx.configDebug?.selectedProfile?.name;
|
|
5805
|
+
pushMessage('system', setSkillSuggestions(slash.on, {
|
|
5806
|
+
cwd: ctx.cwd,
|
|
5807
|
+
...(profile ? { profile } : {}),
|
|
5808
|
+
}).message);
|
|
5809
|
+
return;
|
|
5810
|
+
}
|
|
5400
5811
|
case 'resume':
|
|
5401
5812
|
void doResume();
|
|
5402
5813
|
return;
|
|
@@ -5457,6 +5868,8 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5457
5868
|
case 'none':
|
|
5458
5869
|
return;
|
|
5459
5870
|
case 'host-command': {
|
|
5871
|
+
if (scheduleRef.current?.handleSlash(slash.name, slash.args))
|
|
5872
|
+
return;
|
|
5460
5873
|
// Dispatched through the kernel's registry, built with what
|
|
5461
5874
|
// THIS session can answer from. The descriptors used for
|
|
5462
5875
|
// the merge above carry no store — they are names — so the
|
|
@@ -5761,6 +6174,21 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5761
6174
|
runConversationExport({ kind: 'file', path: slash.path });
|
|
5762
6175
|
return;
|
|
5763
6176
|
}
|
|
6177
|
+
case 'browser': {
|
|
6178
|
+
const generation = conversationGenRef.current;
|
|
6179
|
+
void runBrowserSlash(session?.browser, slash.args, {
|
|
6180
|
+
busy: state !== 'idle' || abortRef.current !== null,
|
|
6181
|
+
}).then((answer) => {
|
|
6182
|
+
// A later session keeps the operator's choice: `/model`
|
|
6183
|
+
// rebuilds the session, and the profile goes with it.
|
|
6184
|
+
if (answer.profile)
|
|
6185
|
+
browserProfileRef.current = answer.profile;
|
|
6186
|
+
if (conversationGenRef.current !== generation)
|
|
6187
|
+
return;
|
|
6188
|
+
pushMessage('system', answer.text);
|
|
6189
|
+
});
|
|
6190
|
+
return;
|
|
6191
|
+
}
|
|
5764
6192
|
default: {
|
|
5765
6193
|
// Exhaustive on purpose. Without it a `SlashAction` kind added
|
|
5766
6194
|
// and not handled here falls out of the switch into the send
|
|
@@ -5786,8 +6214,16 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5786
6214
|
kind: 'human',
|
|
5787
6215
|
text: outgoing,
|
|
5788
6216
|
...(attachments && attachments.length > 0 ? { attachments: [...attachments] } : {}),
|
|
6217
|
+
...(skillFlow ? { skillFlow: true } : {}),
|
|
5789
6218
|
};
|
|
5790
|
-
|
|
6219
|
+
// `/skills save` and `/skills new` are turns of their own: steered into
|
|
6220
|
+
// a running turn, they would lose the flag that keeps the skill-making
|
|
6221
|
+
// turn from being proposed as a skill, and the model would read them
|
|
6222
|
+
// mid-task. They wait in the queue for the turn to end.
|
|
6223
|
+
if (skillFlow && activeTurnInboxRef.current) {
|
|
6224
|
+
pushMessage('system', 'Queued: this runs when the current turn ends.');
|
|
6225
|
+
}
|
|
6226
|
+
if (mode === 'submit' && !skillFlow) {
|
|
5791
6227
|
const inbox = activeTurnInboxRef.current;
|
|
5792
6228
|
if (inbox) {
|
|
5793
6229
|
const expanded = expandFileMentions(outgoing, ctx.cwd);
|
|
@@ -5805,6 +6241,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5805
6241
|
activeSkills,
|
|
5806
6242
|
advanceQueueContinuation,
|
|
5807
6243
|
applyPermissionMode,
|
|
6244
|
+
conversationLearning,
|
|
5808
6245
|
applyReasoningEffort,
|
|
5809
6246
|
appLifetime,
|
|
5810
6247
|
ctx.cwd,
|
|
@@ -5841,6 +6278,40 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5841
6278
|
// a ref so selecting a help row re-enters the one ordinary slash-command
|
|
5842
6279
|
// path instead of growing a second command executor.
|
|
5843
6280
|
commandPickerSubmitRef.current = handleSubmit;
|
|
6281
|
+
saveSkillNotifyRef.current = () => sendTerminalNotification({ kind: 'approval-required' });
|
|
6282
|
+
scheduleLiveRef.current = {
|
|
6283
|
+
...(scheduleLiveRef.current?.model ? { model: scheduleLiveRef.current.model } : {}),
|
|
6284
|
+
say: (text) => {
|
|
6285
|
+
closeLiveReply();
|
|
6286
|
+
pushMessage('system', text);
|
|
6287
|
+
},
|
|
6288
|
+
ask: askQuestion,
|
|
6289
|
+
askPermission: onPermission,
|
|
6290
|
+
submit: (text) => handleSubmit(text),
|
|
6291
|
+
};
|
|
6292
|
+
// Scheduled work since the TUI last looked: one line, after the first
|
|
6293
|
+
// paint, never blocking it. Loops fire when a turn ends, and on their timer.
|
|
6294
|
+
useEffect(() => {
|
|
6295
|
+
const timer = setTimeout(() => {
|
|
6296
|
+
const line = scheduleRef.current?.startupLine();
|
|
6297
|
+
if (line)
|
|
6298
|
+
scheduleLiveRef.current?.say(line);
|
|
6299
|
+
}, 250);
|
|
6300
|
+
return () => {
|
|
6301
|
+
clearTimeout(timer);
|
|
6302
|
+
scheduleRef.current?.dispose();
|
|
6303
|
+
};
|
|
6304
|
+
}, []);
|
|
6305
|
+
useEffect(() => {
|
|
6306
|
+
if (state !== 'idle')
|
|
6307
|
+
return;
|
|
6308
|
+
try {
|
|
6309
|
+
scheduleRef.current?.loops.tick();
|
|
6310
|
+
}
|
|
6311
|
+
catch {
|
|
6312
|
+
// A loop that cannot be read never takes the screen down with it.
|
|
6313
|
+
}
|
|
6314
|
+
}, [state]);
|
|
5844
6315
|
// Keep the footer on the same durable goal record the driver mutates. A ref
|
|
5845
6316
|
// alone cannot repaint React, so every goal mutation bumps goalDriveVersion;
|
|
5846
6317
|
// generation/session checks stop a late disk read labelling a resumed chat.
|
|
@@ -6149,6 +6620,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6149
6620
|
useInput((input, key) => {
|
|
6150
6621
|
if (outputViewer !== null)
|
|
6151
6622
|
return;
|
|
6623
|
+
// The save-skill screen owns its keys, Esc and Ctrl+C included: neither
|
|
6624
|
+
// may interrupt the turn that is waiting on its answer. A permission
|
|
6625
|
+
// request (an agent's, say) takes the screen over it and gets the keys.
|
|
6626
|
+
if (saveSkillPromptRef.current && permission === null)
|
|
6627
|
+
return;
|
|
6152
6628
|
// Startup has refused admission, so there is no draft or active turn to
|
|
6153
6629
|
// protect with the ready screen's two-press exit ladder.
|
|
6154
6630
|
if (phase === 'unhealthy') {
|
|
@@ -6262,7 +6738,8 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6262
6738
|
}
|
|
6263
6739
|
if (permission && (key.upArrow || key.downArrow)) {
|
|
6264
6740
|
const current = permissionChoiceRef.current;
|
|
6265
|
-
const
|
|
6741
|
+
const last = permissionAnswers(permission.toolCalls, { batchOnly: permission.batchOnly === true }).length - 1;
|
|
6742
|
+
const next = key.upArrow ? Math.max(0, current - 1) : Math.min(last, current + 1);
|
|
6266
6743
|
setPermissionChoice(next);
|
|
6267
6744
|
return;
|
|
6268
6745
|
}
|
|
@@ -6301,14 +6778,25 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6301
6778
|
// the operator never looked at. Refusals above never wait.
|
|
6302
6779
|
if (!approvalIsDeliberate(permissionOpenedAtRef.current, Date.now()))
|
|
6303
6780
|
return;
|
|
6304
|
-
|
|
6781
|
+
// The answers on screen, so a digit or Enter means what it is next
|
|
6782
|
+
// to: a batch-only prompt (a scheduled run) has no "allow all", and
|
|
6783
|
+
// neither its `a` nor a `3` answers it.
|
|
6784
|
+
const answers = permissionAnswers(permission?.toolCalls ?? [], {
|
|
6785
|
+
batchOnly: permission?.batchOnly === true,
|
|
6786
|
+
});
|
|
6787
|
+
const numbered = /^[1-9]$/.test(ch) ? Number(ch) - 1 : null;
|
|
6305
6788
|
const chosen = key.return ? permissionChoiceRef.current : numbered;
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6789
|
+
const kind = ch === 'y'
|
|
6790
|
+
? 'approve'
|
|
6791
|
+
: ch === 'a'
|
|
6792
|
+
? answers.some((answer) => answer.kind === 'approve-all')
|
|
6793
|
+
? 'approve-all'
|
|
6794
|
+
: undefined
|
|
6795
|
+
: chosen !== null
|
|
6796
|
+
? answers[chosen]?.kind
|
|
6797
|
+
: undefined;
|
|
6798
|
+
if (kind)
|
|
6799
|
+
resolvePermission({ kind });
|
|
6312
6800
|
return;
|
|
6313
6801
|
}
|
|
6314
6802
|
// The active child count already advertises this key beside Working.
|
|
@@ -6614,6 +7102,21 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6614
7102
|
resetTranscript();
|
|
6615
7103
|
return;
|
|
6616
7104
|
}
|
|
7105
|
+
// A turn a tool paused for a person: Enter on an empty composer
|
|
7106
|
+
// continues it, Esc stops it (the turn is abandoned, like /abandon).
|
|
7107
|
+
if (handoffParkRef.current &&
|
|
7108
|
+
phase === 'ready' &&
|
|
7109
|
+
state === 'idle' &&
|
|
7110
|
+
!abortRef.current &&
|
|
7111
|
+
((key.return && !composerHasDraft) || key.escape)) {
|
|
7112
|
+
const parked = handoffParkRef.current;
|
|
7113
|
+
setHandoffPark(null);
|
|
7114
|
+
if (key.return)
|
|
7115
|
+
void resumeActiveTurn({ handoffReason: parked.reason });
|
|
7116
|
+
else
|
|
7117
|
+
void abandonActiveTurn('The operator stopped the turn a tool paused for them.');
|
|
7118
|
+
return;
|
|
7119
|
+
}
|
|
6617
7120
|
// Esc interrupts a running turn (Ctrl+C stays reserved for exit). Mirrors
|
|
6618
7121
|
// the Ctrl+C interrupt path: abort, drop the queue, one "Interrupted." line.
|
|
6619
7122
|
if (key.escape && (abortRef.current || pendingModelSwitchRef.current)) {
|
|
@@ -6738,29 +7241,33 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6738
7241
|
? 'starting a fresh conversation — input is paused'
|
|
6739
7242
|
: compacting
|
|
6740
7243
|
? 'compacting conversation — input is paused'
|
|
6741
|
-
:
|
|
6742
|
-
?
|
|
6743
|
-
:
|
|
6744
|
-
?
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
? '
|
|
6748
|
-
:
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
:
|
|
6752
|
-
? '
|
|
6753
|
-
:
|
|
6754
|
-
?
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
? '
|
|
6758
|
-
:
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
:
|
|
6762
|
-
?
|
|
6763
|
-
:
|
|
7244
|
+
: saveSkillPrompt
|
|
7245
|
+
? 'save skill — ←/→ choose · enter apply · esc cancel'
|
|
7246
|
+
: permission
|
|
7247
|
+
? hintForPhase(phase, state, session?.hasProvider === true)
|
|
7248
|
+
: agentSurface?.kind === 'cockpit'
|
|
7249
|
+
? agentSurface.focus === 'workflows'
|
|
7250
|
+
? 'workflows — enter select · esc return'
|
|
7251
|
+
: agentSurface.focus === 'phases'
|
|
7252
|
+
? 'agent phases — enter agents · esc return'
|
|
7253
|
+
: 'agents — enter inspect · left phases · esc return'
|
|
7254
|
+
: agentSurface?.kind === 'transcript'
|
|
7255
|
+
? 'observing agent — esc agents · q parent'
|
|
7256
|
+
: textPrompt
|
|
7257
|
+
? 'editor open · enter apply · esc cancel'
|
|
7258
|
+
: choicePicker
|
|
7259
|
+
? choicePickerSearchable(choicePicker)
|
|
7260
|
+
? 'type to filter · ↑↓ select · enter apply · esc back'
|
|
7261
|
+
: effortSlider
|
|
7262
|
+
? '←/→ adjust · 1–9 select · enter apply · esc back'
|
|
7263
|
+
: '↑↓ / 1–9 select · enter apply · esc back'
|
|
7264
|
+
: copyPicker
|
|
7265
|
+
? 'copy target open — ↑↓ / 1–9 select · esc cancel'
|
|
7266
|
+
: handoffPark && phase === 'ready' && state === 'idle' && !composerHasDraft
|
|
7267
|
+
? 'enter continue · esc stop'
|
|
7268
|
+
: goalStatus && phase === 'ready' && state === 'idle'
|
|
7269
|
+
? undefined
|
|
7270
|
+
: hintForPhase(phase, state, session?.hasProvider === true, composerHasDraft);
|
|
6764
7271
|
const permissionSource = reviewSource(permission, scopeRef.current?.sessionId);
|
|
6765
7272
|
const permissionOwner = permissionSource
|
|
6766
7273
|
? subagents.find((agent) => agent.sessionId === permissionSource)
|
|
@@ -6799,7 +7306,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6799
7306
|
// where it is decided rather than guessed on the screen: with no
|
|
6800
7307
|
// session behind the picker, esc exits namzu and the footer says
|
|
6801
7308
|
// so instead of offering a cancel nobody will see.
|
|
6802
|
-
cancelExits: !session?.hasProvider })) : (_jsxs(_Fragment, { children: [agentSurface === null && outputViewer === null ? (_jsx(LiveActivity, { compact: compactWork, activeTools: visibleActiveTools, working: state === 'thinking' || state === 'tool', interruptible: abortRef.current !== null, animate: stdout.isTTY === true &&
|
|
7309
|
+
cancelExits: !session?.hasProvider })) : (_jsxs(_Fragment, { children: [agentSurface === null && outputViewer === null ? (_jsx(LiveActivity, { compact: compactWork, activeTools: visibleActiveTools, working: state === 'thinking' || state === 'tool', interruptible: abortRef.current !== null, animate: stdout.isTTY === true &&
|
|
7310
|
+
permission === null &&
|
|
7311
|
+
textPrompt === null &&
|
|
7312
|
+
saveSkillPrompt === null, thinking: thinking, tokens: turnTokens, waitingForYou: choicePicker !== null ||
|
|
7313
|
+
permission !== null ||
|
|
7314
|
+
textPrompt !== null ||
|
|
7315
|
+
saveSkillPrompt !== null })) : null, agentSurface === null && outputViewer === null && permission === null ? (_jsx(TaskList, { tasks: liveTasks })) : null, permission ? (_jsx(PermissionOverlay, { toolCalls: permission.toolCalls, review: permission.review, summary: permission.summary, detailsOpen: permissionDetailsOpen, reviewOffset: permissionReviewOffset, choice: permissionChoice, queuedCount: queuedPermissionCount, sourceLabel: permissionSourceLabel, columns: terminal.columns, rows: terminal.rows, batchOnly: permission.batchOnly === true, siteNotes: permission.siteNotes })) : null, saveSkillPrompt && permission === null ? (_jsx(SaveSkillOverlay, { request: saveSkillPrompt.request, cwd: ctx.cwd, columns: Math.max(1, (terminal.columns ?? 80) - 2), rows: terminal.rows, onAnswer: saveSkillPrompt.resolve }, saveSkillPrompt.request.markdown)) : null, textPrompt ? (_jsx(TextPrompt, { columns: Math.max(1, (terminal.columns ?? 80) - 2), title: textPrompt.title, placeholder: textPrompt.placeholder, initialValue: textPrompt.initialValue, emptyNotice: textPrompt.emptyNotice, hidden: permission !== null || (agentSurface !== null || outputViewer !== null), onSubmit: submitTextPrompt, onCancel: cancelTextPrompt }, textPrompt.token)) : permission === null &&
|
|
6803
7316
|
agentSurface === null &&
|
|
6804
7317
|
outputViewer === null &&
|
|
6805
7318
|
choicePicker?.kind === 'reasoning-effort' &&
|
|
@@ -6811,7 +7324,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6811
7324
|
textPrompt === null &&
|
|
6812
7325
|
choicePicker === null &&
|
|
6813
7326
|
copyPicker === null &&
|
|
7327
|
+
saveSkillPrompt === null &&
|
|
6814
7328
|
agentSurface === null && outputViewer === null, hidden: permission !== null ||
|
|
7329
|
+
saveSkillPrompt !== null ||
|
|
6815
7330
|
textPrompt !== null ||
|
|
6816
7331
|
choicePicker !== null ||
|
|
6817
7332
|
copyPicker !== null ||
|
|
@@ -6836,14 +7351,18 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6836
7351
|
conversationMutation !== null ||
|
|
6837
7352
|
externalEditorRequest !== null ||
|
|
6838
7353
|
(agentSurface !== null || outputViewer !== null), hidden: permission !== null ||
|
|
7354
|
+
saveSkillPrompt !== null ||
|
|
6839
7355
|
textPrompt !== null ||
|
|
6840
7356
|
choicePicker !== null ||
|
|
6841
7357
|
copyPicker !== null ||
|
|
6842
7358
|
(agentSurface !== null || outputViewer !== null),
|
|
6843
7359
|
// A turn is running, so Esc is the interrupt and not
|
|
6844
7360
|
// the composer's clear.
|
|
6845
|
-
escapeInterrupts: !compacting &&
|
|
6846
|
-
(state === 'thinking' ||
|
|
7361
|
+
escapeInterrupts: (!compacting &&
|
|
7362
|
+
(state === 'thinking' ||
|
|
7363
|
+
state === 'tool' ||
|
|
7364
|
+
pendingModelSwitchRef.current !== null)) ||
|
|
7365
|
+
(handoffPark !== null && state === 'idle'), onSubmit: handleSubmit, onNotice: (text) => pushMessage('system', text), onStepReasoningEffort: stepReasoningEffort, onExternalEdit: requestExternalEditor, onEditPrevious: openPromptEditor, draftToRestore: composerDraft, onDraftRestored: (token) => setComposerDraft((draft) => (draft?.token === token ? null : draft)), onDraftPresenceChange: setComposerHasDraft, onOpenAgentPanel: openAgentCockpit, userCommands: userCommands, builtins: hostCommands.map((command) => ({
|
|
6847
7366
|
...command,
|
|
6848
7367
|
description: command.unavailable?.(slashCtx) ?? command.description,
|
|
6849
7368
|
})), mentionCandidates: mentionCandidates, history: history })] })] })), _jsx(StatusBar, { cwd: ctx.cwd, provider: session?.providerSummary ?? null, model: session?.modelSummary ?? null, effort: reasoningEffort, orchestrate: orchestrateMode, goal: statusGoal, state: state, hint: statusHint, permissionMode: displayedPermissionMode, canCycleMode: true }), showComposerSurface &&
|