Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6f94970
feat(goal): make Goal the sole long-task runtime
SivanCola Aug 8, 2026
0395830
fix(agent): hide context-unavailable tools
SivanCola Aug 8, 2026
eabcc35
chore(lint): account for contextual tool coverage
SivanCola Aug 8, 2026
5a3965e
Merge origin/main-v2 into feature/goal-sole-runtime
SivanCola Aug 8, 2026
79b8075
Merge latest origin/main-v2 into feature/goal-sole-runtime
SivanCola Aug 8, 2026
c459e8a
chore(lint): baseline latest governor growth
SivanCola Aug 8, 2026
a8a2a83
fix(goal): harden legacy migration and contextual tools
SivanCola Aug 8, 2026
a8ba822
fix(goal): close legacy restore and schema isolation gaps
SivanCola Aug 8, 2026
c49f2fb
Merge remote-tracking branch 'origin/main-v2' into feature/goal-sole-…
SivanCola Aug 8, 2026
de9293a
test(goal): make unreadable archive fixture portable
SivanCola Aug 8, 2026
eeae3e5
merge: reconcile concurrent goal runtime fixes
SivanCola Aug 8, 2026
3eb922b
Merge remote-tracking branch 'refs/remotes/sivancola/feature/goal-sol…
SivanCola Aug 8, 2026
bf47eed
fix(goal): restore contextual tool and migration contracts
SivanCola Aug 8, 2026
2809240
Merge origin/main-v2 into feature/goal-sole-runtime
SivanCola Aug 8, 2026
4636cc1
Fix legacy Goal recovery boundary
SivanCola Aug 8, 2026
e9f0851
Merge remote-tracking branch 'origin/main-v2' into feature/goal-sole-…
SivanCola Aug 8, 2026
e08ffbf
refactor: isolate contextual workflow surfaces
SivanCola Aug 8, 2026
a625602
Merge remote-tracking branch 'origin/main-v2' into feature/goal-sole-…
SivanCola Aug 8, 2026
f2929dc
fix: preserve the static provider tool contract
SivanCola Aug 8, 2026
ccca8fc
fix: complete the Goal-only compatibility boundary
SivanCola Aug 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ branch.

### Fixed

- Goal is now the sole long-task runtime. Historical AutoResearch sidecars
migrate transactionally into research-budget Goals. Invalid archives block
fail closed and remain read-only; successful Goal-only sidecars omit the old
task id and write an explicit downgrade fence so previous readers cannot
reactivate the removed runtime.

- **Issue #7575:** Linux Bash under bubblewrap no longer mounts a fresh empty
`--tmpfs /tmp` on every call. Consecutive commands in the same logical session
now share a private temporary directory (bound at `/tmp` on Linux, exported via
Expand Down
243 changes: 19 additions & 224 deletions desktop/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import (
"github.com/wailsapp/wails/v2/pkg/runtime"

"reasonix/internal/agent"
"reasonix/internal/autoresearch"
"reasonix/internal/billing"
"reasonix/internal/boot"
"reasonix/internal/botruntime"
Expand Down Expand Up @@ -6927,26 +6926,25 @@ func (a *App) jobsForCtrl(ctrl control.SessionAPI, out []JobView) []JobView {

// Meta describes the session for the frontend's header and status line.
type Meta struct {
Label string `json:"label"`
Ready bool `json:"ready"`
Runtime SessionRuntimeView `json:"runtime"`
StartupErr string `json:"startupErr,omitempty"`
EventChannel string `json:"eventChannel"`
Cwd string `json:"cwd"`
WorkspaceRoot string `json:"workspaceRoot,omitempty"`
WorkspaceName string `json:"workspaceName,omitempty"`
WorkspacePath string `json:"workspacePath,omitempty"`
GitBranch string `json:"gitBranch,omitempty"`
ImageInputEnabled bool `json:"imageInputEnabled"`
AutoApproveTools bool `json:"autoApproveTools"`
Bypass bool `json:"bypass"` // legacy JSON key for YOLO/full-access tool auto-approval
CollaborationMode string `json:"collaborationMode"`
ToolApprovalMode string `json:"toolApprovalMode"`
TokenMode string `json:"tokenMode"`
Goal string `json:"goal,omitempty"`
GoalStatus string `json:"goalStatus,omitempty"`
GoalRuntime *GoalRuntimeView `json:"goalRuntime,omitempty"`
AutoResearch *AutoResearchCompactView `json:"autoResearch,omitempty"`
Label string `json:"label"`
Ready bool `json:"ready"`
Runtime SessionRuntimeView `json:"runtime"`
StartupErr string `json:"startupErr,omitempty"`
EventChannel string `json:"eventChannel"`
Cwd string `json:"cwd"`
WorkspaceRoot string `json:"workspaceRoot,omitempty"`
WorkspaceName string `json:"workspaceName,omitempty"`
WorkspacePath string `json:"workspacePath,omitempty"`
GitBranch string `json:"gitBranch,omitempty"`
ImageInputEnabled bool `json:"imageInputEnabled"`
AutoApproveTools bool `json:"autoApproveTools"`
Bypass bool `json:"bypass"` // legacy JSON key for YOLO/full-access tool auto-approval
CollaborationMode string `json:"collaborationMode"`
ToolApprovalMode string `json:"toolApprovalMode"`
TokenMode string `json:"tokenMode"`
Goal string `json:"goal,omitempty"`
GoalStatus string `json:"goalStatus,omitempty"`
GoalRuntime *GoalRuntimeView `json:"goalRuntime,omitempty"`
// A nil pointer means the controller cannot provide an authoritative snapshot;
// a non-nil pointer preserves an empty list as an explicit panel clear.
CanonicalTodos *[]evidence.TodoItem `json:"canonicalTodos,omitempty"`
Expand Down Expand Up @@ -6983,60 +6981,6 @@ func goalRuntimeViewFromController(ctrl control.SessionAPI) *GoalRuntimeView {
}
}

type AutoResearchCompactView struct {
TaskID string `json:"taskId"`
Status string `json:"status"`
Iteration int `json:"iteration"`
PivotRequired bool `json:"pivotRequired"`
StaleCount int `json:"staleCount"`
}

type AutoResearchCriterionView struct {
ID string `json:"id"`
Description string `json:"description"`
Required bool `json:"required"`
EvidenceCount int `json:"evidenceCount"`
Status string `json:"status"`
}

type AutoResearchStatusView struct {
TaskID string `json:"taskId"`
Goal string `json:"goal"`
Status string `json:"status"`
Iteration int `json:"iteration"`
CurrentDirection string `json:"currentDirection"`
StaleCount int `json:"staleCount"`
PivotCount int `json:"pivotCount"`
PivotRequired bool `json:"pivotRequired"`
LastHeartbeatAt string `json:"lastHeartbeatAt"`
FindingCount int `json:"findingCount"`
OpenCriteria []AutoResearchCriterionView `json:"openCriteria"`
Blocker string `json:"blocker"`
TaskPath string `json:"taskPath"`
NextRequiredAction string `json:"nextRequiredAction"`
}

type AutoResearchFindingView struct {
ID string `json:"id"`
Kind string `json:"kind"`
Summary string `json:"summary"`
Source string `json:"source"`
Command string `json:"command,omitempty"`
Paths []string `json:"paths,omitempty"`
Accepted bool `json:"accepted"`
CreatedAt string `json:"createdAt"`
}

type AutoResearchEvidenceView struct {
ID string `json:"id"`
Kind string `json:"kind"`
Summary string `json:"summary"`
Source string `json:"source"`
Command string `json:"command,omitempty"`
Paths []string `json:"paths,omitempty"`
Accepted bool `json:"accepted"`
}

// Meta reports the model label, readiness, any startup error, the working
// directory (for the status line), and the runtime event channel the frontend
// subscribes to.
Expand Down Expand Up @@ -7112,29 +7056,10 @@ func (a *App) MetaForTab(tabID string) Meta {
Goal: goal,
GoalStatus: goalStatus,
GoalRuntime: goalRuntimeViewFromController(snap.ctrl),
AutoResearch: compactAutoResearchFromController(snap.ctrl),
CanonicalTodos: ctrlTodos(snap.ctrl),
}
}

func compactAutoResearchFromController(ctrl control.SessionAPI) *AutoResearchCompactView {
if ctrl == nil {
return nil
}

summary, ok := ctrl.AutoResearchSummary()
if !ok || summary == nil || summary.TaskID == "" {
return nil
}
return &AutoResearchCompactView{
TaskID: summary.TaskID,
Status: summary.Status,
Iteration: summary.Iteration,
PivotRequired: summary.PivotRequired,
StaleCount: summary.StaleCount,
}
}

// ctrlTodos returns the canonical task list from a session controller, or nil
// if the controller is not yet bound. Used by MetaForTab so the frontend
// task panel has access to the authoritative server-side todo state.
Expand All @@ -7149,136 +7074,6 @@ func ctrlTodos(ctrl control.SessionAPI) *[]evidence.TodoItem {
return &todos
}

func compactAutoResearch(tab *WorkspaceTab) *AutoResearchCompactView {
if tab == nil || tab.Ctrl == nil {
return nil
}
summary, ok := tab.Ctrl.AutoResearchSummary()
if !ok || summary == nil || summary.TaskID == "" {
return nil
}
return &AutoResearchCompactView{
TaskID: summary.TaskID,
Status: summary.Status,
Iteration: summary.Iteration,
PivotRequired: summary.PivotRequired,
StaleCount: summary.StaleCount,
}
}

func autoResearchStatusView(summary *autoresearch.Summary) AutoResearchStatusView {
if summary == nil {
return AutoResearchStatusView{OpenCriteria: []AutoResearchCriterionView{}}
}
open := make([]AutoResearchCriterionView, 0, len(summary.OpenCriteria))
for _, criterion := range summary.OpenCriteria {
open = append(open, AutoResearchCriterionView{
ID: criterion.ID,
Description: criterion.Description,
Required: criterion.Required,
EvidenceCount: criterion.EvidenceCount,
Status: criterion.Status,
})
}
return AutoResearchStatusView{
TaskID: summary.TaskID,
Goal: summary.Goal,
Status: summary.Status,
Iteration: summary.Iteration,
CurrentDirection: summary.CurrentDirection,
StaleCount: summary.StaleCount,
PivotCount: summary.PivotCount,
PivotRequired: summary.PivotRequired,
LastHeartbeatAt: summary.LastHeartbeatAt.Format(time.RFC3339),
FindingCount: summary.FindingCount,
OpenCriteria: open,
Blocker: summary.Blocker,
TaskPath: summary.TaskPath,
NextRequiredAction: summary.NextRequiredAction,
}
}

func (a *App) AutoResearchCurrent() AutoResearchStatusView {
return a.AutoResearchStatus("")
}

func (a *App) AutoResearchStatus(tabID string) AutoResearchStatusView {
ctrl := a.ctrlByTabID(tabID)
if ctrl == nil {
return AutoResearchStatusView{OpenCriteria: []AutoResearchCriterionView{}}
}
summary, ok := ctrl.AutoResearchSummary()
if !ok {
return AutoResearchStatusView{OpenCriteria: []AutoResearchCriterionView{}}
}
return autoResearchStatusView(summary)
}

func (a *App) AutoResearchList(tabID string) []AutoResearchStatusView {
ctrl := a.ctrlByTabID(tabID)
if ctrl == nil {
return []AutoResearchStatusView{}
}
summaries, ok := ctrl.AutoResearchList()
if !ok {
return []AutoResearchStatusView{}
}
out := make([]AutoResearchStatusView, 0, len(summaries))
for i := range summaries {
out = append(out, autoResearchStatusView(&summaries[i]))
}
return out
}

func (a *App) AutoResearchFindings(tabID string, limit int) []AutoResearchFindingView {
ctrl := a.ctrlByTabID(tabID)
if ctrl == nil {
return []AutoResearchFindingView{}
}
findings, ok := ctrl.AutoResearchFindings(limit)
if !ok {
return []AutoResearchFindingView{}
}
out := make([]AutoResearchFindingView, 0, len(findings))
for _, finding := range findings {
out = append(out, AutoResearchFindingView{
ID: finding.ID,
Kind: finding.Kind,
Summary: finding.Summary,
Source: finding.Source,
Command: finding.Command,
Paths: append([]string(nil), finding.Paths...),
Accepted: finding.Accepted,
CreatedAt: finding.CreatedAt.Format(time.RFC3339),
})
}
return out
}

func (a *App) AutoResearchOpenTask(tabID string) error {
status := a.AutoResearchStatus(tabID)
if strings.TrimSpace(status.TaskPath) == "" {
return os.ErrInvalid
}
return a.RevealPath(status.TaskPath)
}

func (a *App) AutoResearchRecordEvidence(tabID, criterionID string, input AutoResearchEvidenceView) error {
ctrl := a.ctrlByTabID(tabID)
if ctrl == nil {
return os.ErrInvalid
}
return ctrl.RecordAutoResearchEvidence(criterionID, control.AutoResearchEvidenceInput{
ID: input.ID,
Kind: input.Kind,
Summary: input.Summary,
Source: input.Source,
Command: input.Command,
Paths: append([]string(nil), input.Paths...),
Accepted: input.Accepted,
})
}

func (a *App) SetGoal(goal string) error {
return a.SetGoalForTab("", goal)
}
Expand Down
2 changes: 0 additions & 2 deletions desktop/bound_array_contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ func TestBoundArrayPayloadsAreNonNilBeforeStartup(t *testing.T) {
{"ListTabs", app.ListTabs()},
{"ListProjectTree", app.ListProjectTree()},
{"AvailableSubagentTools", app.AvailableSubagentTools()},
{"AutoResearchList", app.AutoResearchList("missing")},
{"AutoResearchFindings", app.AutoResearchFindings("missing", 10)},
{"MCPServers", app.MCPServers()},
{"Plugins", app.Plugins()},
{"HeartbeatListTasks", app.HeartbeatListTasks()},
Expand Down
10 changes: 4 additions & 6 deletions desktop/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,6 @@ function noticePreviewItems(): Item[] {
notice(8, "info", "Context was compacted without a generated summary.", "compaction completed after upstream summary generation returned empty content; retained transcript checkpoint"),
notice(9, "info", "Goal is not ready to complete yet; continuing the remaining work.", "goal completion check found pending validation: desktop/frontend typecheck"),
notice(13, "info", "Goal still has unfinished task state; continuing the remaining work.", "active goal has open task state: implement preview, verify browser, report result"),
notice(14, "warn", "AutoResearch status update failed.", "autoresearch task completion update failed: write .reasonix/autoresearch/task-42/state/task_spec.json: permission denied"),
notice(15, "warn", "AutoResearch task marked blocked.", "autoresearch task blocked: task-42\nreason: missing accepted verification evidence after three turns"),
notice(16, "warn", "background export failed: needs attention", "background export failed: session archive upload returned 503 after 3 retries"),
notice(17, "warn", "Job artifact migration failed.", "artifact migration failed for job job_123: checksum mismatch while moving output.zip"),
notice(18, "warn", "Background job teardown timed out.", "job job_123 did not stop within 10s; process is still marked running by the supervisor"),
Expand Down Expand Up @@ -299,7 +297,7 @@ const CHAT_MIN_WIDTH = 400;
const CHAT_COMFORT_MIN_WIDTH = 560;
const WORKSPACE_RESIZER_WIDTH = 8;

function stripGoalResearchFlags(arg: string): string {
function stripLegacyGoalBudgetFlags(arg: string): string {
const parts = arg.trim().split(/\s+/).filter(Boolean);
while (parts.length > 0) {
const flag = parts[0].toLowerCase();
Expand All @@ -309,7 +307,7 @@ function stripGoalResearchFlags(arg: string): string {
return parts.join(" ");
}

function hasGoalResearchFlag(arg: string): boolean {
function hasLegacyGoalBudgetFlag(arg: string): boolean {
const first = arg.trim().split(/\s+/, 1)[0]?.toLowerCase();
return first === "--research" || first === "--auto-research" || first === "--deep" || first === "--simple" || first === "--no-research";
}
Expand Down Expand Up @@ -2380,9 +2378,9 @@ export default function App() {
const goalCommand = /^\/goal(?:\s+(.*))?$/.exec(trimmed);
if (goalCommand) {
const arg = (goalCommand[1] ?? "").trim();
const displayGoal = stripGoalResearchFlags(arg);
const displayGoal = stripLegacyGoalBudgetFlags(arg);
if (displayGoal && !["status", "clear", "off", "stop", "done"].includes(displayGoal.toLowerCase())) {
if (hasGoalResearchFlag(arg)) {
if (hasLegacyGoalBudgetFlag(arg)) {
userPlanModeByTabRef.current = updateUserPlanModeIntent(userPlanModeByTabRef.current, activeTabId, false);
patchActiveComposerProfile({
collaborationMode: "goal",
Expand Down
Loading
Loading