Skip to content

feat: AI-native coding workspace with spec mode and async subagents#62

Merged
vi70x4 merged 8 commits into
mainfrom
feat/coding-workspace
Jun 30, 2026
Merged

feat: AI-native coding workspace with spec mode and async subagents#62
vi70x4 merged 8 commits into
mainfrom
feat/coding-workspace

Conversation

@vi70x4

@vi70x4 vi70x4 commented Jun 30, 2026

Copy link
Copy Markdown
Member

Summary\n- Adds shared coding workspace contracts, code-intelligence facade, spec mode state machine, async native subagent job model, tool definitions, prompt contributions, and compact chat renderers under packages/stage-ui/src/coding-workspace/\n- Integrates coding workspace into the Tamagotchi renderer via a dedicated store, native-only Serena-preferred engine, merged chat renderers, compact chat controls, and MCP bridge with refresh-based backend detection\n- Extends MCP settings UI with a Serena preset and updates the stage-ui exports contract\n- Normal AIRI chat remains the default experience; coding tools and prompts register only when coding context is enabled\n\n## Verification\n- Stage UI coding-workspace module tests: 5 files, 30 tests passed\n- Tamagotchi store tests: 2 files, 7 tests passed\n- Stage UI export contract test: passed\n- @proj-airi/stage-ui vue-tsc --noEmit: passed\n- @proj-airi/stage-tamagotchi vue-tsc --noEmit: passed\n\n## Deferred follow-ups before ACP or swarm runtime\n- Connect workspace_update_spec_artifact to AIRI-owned workspace persistence\n- Persist coding workspace state per chat session\n- Add approval-backed filesystem, patch, terminal, and command tools\n- Native swarm runtime for async subagent job execution\n- ACP/Pi/Codex subprocess engines\n\n## Test plan\n- [x] Run pnpm test across affected packages\n- [x] Run both package typechecks\n- [x] Review coding workspace store wiring and MCP backend detection\n- [x] Review chat renderer merge in InteractiveArea.vue\n\n🤖 Generated with Factory

vi70x3 and others added 7 commits June 30, 2026 00:00
Summarizes the AIRI-native coding workspace architecture, Serena/MCP code intelligence, guarded workspace tools, and optional ACP subprocess engines for Pi/Codex.

Tests: rg -n "TBD|TODO|PLACEHOLDER|FIXME|\?\?" docs/architecture/coding-workspace.md
Tests: sed -n '1,420p' docs/architecture/coding-workspace.md

Follow-up: review and resolve open architecture questions before implementation planning.
Records decisions from spec review: coding controls stay inside the vanilla chat flow and remain unobtrusive, coding state is scoped to coding-enabled chat sessions, and Serena is offered as a predefined MCP settings template with uv/setup guidance rather than auto-installed.

Tests: rg -n "TBD|TODO|PLACEHOLDER|FIXME|\?\?" docs/architecture/coding-workspace.md
Tests: sed -n '100,240p' docs/architecture/coding-workspace.md
Tests: sed -n '350,520p' docs/architecture/coding-workspace.md

Follow-up: decide Architect write approvals and first ACP backend.
Verification:
- rg -n "TBD|TODO|PLACEHOLDER|FIXME|\?\?" docs/specs/coding-workspace-spec-mode-and-subagents/requirements.md
- sed -n '1,260p' docs/specs/coding-workspace-spec-mode-and-subagents/requirements.md
- git diff --cached -- docs/specs/coding-workspace-spec-mode-and-subagents/requirements.md
- git status --short
Verification:
- rg -n "TBD|TODO|PLACEHOLDER|FIXME|\\?\\?" docs/architecture/coding-workspace.md docs/specs/coding-workspace-spec-mode-and-subagents/requirements.md docs/specs/coding-workspace-swarm-runtime/requirements.md
- rg -n "Architect mode|### Architect|all Ask and Architect|Which ACP backend|initial engine identifiers" docs/architecture/coding-workspace.md docs/specs/coding-workspace-spec-mode-and-subagents/requirements.md docs/specs/coding-workspace-swarm-runtime/requirements.md
- sed -n "1,280p" docs/specs/coding-workspace-swarm-runtime/requirements.md
- git diff --cached --stat
- git diff --cached -- docs/architecture/coding-workspace.md docs/specs/coding-workspace-spec-mode-and-subagents/requirements.md docs/specs/coding-workspace-swarm-runtime/requirements.md
- git status --short
Verification:
- rg -n "TBD|TODO|PLACEHOLDER|FIXME|\\?\\?|fill in|implement later|appropriate error handling|Similar to" docs/specs/coding-workspace-spec-mode-and-subagents/tasks.md
- sed -n "1,460p" docs/specs/coding-workspace-spec-mode-and-subagents/tasks.md
- git diff --cached --stat
- git diff --cached -- docs/specs/coding-workspace-spec-mode-and-subagents/tasks.md
- git status --short
Adds the v1 design doc, marks T-001..T-009 complete in the task
tracker, and records the implemented snapshot plus deferred work in the
architecture doc.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…renderers

Completes the Tamagotchi renderer integration for the coding workspace
slice: coding store with native-only engine, coding tool registration
through the LLM tools store, prompt contribution registration, merged
chat renderers, compact chat controls, and MCP backend detection that
resolves to unavailable/available/serena from registered MCP tools.

Extends the MCP settings UI with a Serena server preset and an
updated settings contract test plus package export.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@deepsource-io

deepsource-io Bot commented Jun 30, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in e298f0b...479e940 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

Important

Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
JavaScript Jun 30, 2026 10:10a.m. Review ↗
Shell Jun 30, 2026 10:10a.m. Review ↗
C & C++ Jun 30, 2026 10:10a.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

import { useRouter } from 'vue-router'

import JournalToolCallBlock from './chat-tool-renderers/journal-tool-call-block.vue'
import CodingWorkspaceControls from './coding-workspace/CodingWorkspaceControls.vue'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No default export found in imported module "./coding-workspace/CodingWorkspaceControls.vue"


It is recommended to use default imports only if there is a default export in the module being imported. If there are no default exports in the source module, it is better to use named imports, and it helps the build tools while optimizing the code.

{{ tn('presets.serena.guidance.before-link') }}
<a
:href="SERENA_SETUP_URL"
target="_blank"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using target="_blank" without rel="noopener noreferrer" is a security risk


A malicious actor can gain full control over the user's DOM window object.
This can lead to phishing attacks such as fake login prompts or password alerts being shown to the user.

})

it('registers coding tools and prompt contributions only while coding context is enabled', async () => {
const callMcpTool = vi.fn(async (input) => ({ toolResult: { input } }))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found `async` function without any `await` expressions


A function that does not contain any await expressions should not be async (except for some edge cases in TypeScript which are discussed below). Asynchronous functions in JavaScript behave differently than other functions in two important ways:

Comment on lines +32 to +35
const listMcpTools = vi.fn(async () => [
{ serverName: 'serena', name: 'serena::find_symbol', toolName: 'find_symbol' },
{ serverName: 'serena', name: 'serena::search_for_pattern', toolName: 'search_for_pattern' },
])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found `async` function without any `await` expressions


A function that does not contain any await expressions should not be async (except for some edge cases in TypeScript which are discussed below). Asynchronous functions in JavaScript behave differently than other functions in two important ways:

Comment on lines +41 to +43
async listMcpTools() {
return []
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found `async` function without any `await` expressions


A function that does not contain any await expressions should not be async (except for some edge cases in TypeScript which are discussed below). Asynchronous functions in JavaScript behave differently than other functions in two important ways:

jobId: 'job-1',
status: 'running',
},
toolExecutionContext,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'toolExecutionContext' was used before it was defined


Variables, functions and types should always be used after they've been defined. This issue will flag any code snippets that use variables or types before definition.

metadata: { query: { query: 'workspace' } },
}),
},
toolExecutionContext,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'toolExecutionContext' was used before it was defined


Variables, functions and types should always be used after they've been defined. This issue will flag any code snippets that use variables or types before definition.

Comment on lines +254 to +258
updateSpecArtifact: vi.fn(async (input) => ({
artifactName: input.artifactName,
path: input.path,
content: input.content,
})),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found `async` function without any `await` expressions


A function that does not contain any await expressions should not be async (except for some edge cases in TypeScript which are discussed below). Asynchronous functions in JavaScript behave differently than other functions in two important ways:

Comment on lines +285 to +301
await executeAllowed(runtime, subagentMutationModes, async () => {
const mutation = createSubagentJob(runtime.getSubagentStore(), {
phase: input.phase,
taskDescription: input.taskDescription,
engine: input.engine,
inputs: parseJsonArray<SubagentJobInput>(input.inputsJson, 'inputsJson'),
outputs: parseJsonArray<SubagentJobOutput>(input.outputsJson, 'outputsJson'),
provenance: parseJsonArray<SubagentJobProvenance>(input.provenanceJson, 'provenanceJson'),
})
runtime.setSubagentStore(mutation.store)

return {
ok: true,
jobId: mutation.jobId,
job: mutation.job,
}
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found `async` function without any `await` expressions


A function that does not contain any await expressions should not be async (except for some edge cases in TypeScript which are discussed below). Asynchronous functions in JavaScript behave differently than other functions in two important ways:

Comment on lines +309 to +331
await executeAllowed(runtime, subagentMutationModes, async () => {
const status = input.status ?? runtime.getSubagentStore().jobs[input.jobId as SubagentJobId]?.status
if (!status) {
return failure('job-status-required', 'Provide status when the subagent job cannot be found.')
}

const mutation = transitionSubagentJob(
runtime.getSubagentStore(),
input.jobId as SubagentJobId,
status as SubagentJobStatus,
{
output: parseOptionalJson<SubagentJobOutput>(input.outputJson, 'outputJson'),
provenance: parseOptionalJson<SubagentJobProvenance>(input.provenanceJson, 'provenanceJson'),
},
)
runtime.setSubagentStore(mutation.store)

return {
ok: true,
jobId: mutation.jobId,
job: mutation.job,
}
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found `async` function without any `await` expressions


A function that does not contain any await expressions should not be async (except for some edge cases in TypeScript which are discussed below). Asynchronous functions in JavaScript behave differently than other functions in two important ways:

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an AIRI-native coding workspace, integrating compact controls, a Spec mode state machine, async native subagent job models, and custom chat renderers into the Tamagotchi renderer and Stage UI. It also adds a code-intelligence facade that prefers Serena MCP tools with fallback options. Feedback on the changes suggests localizing hardcoded English strings in the controls component, correcting a redundant ternary expression in the diagnostics model to properly map severity to tone, and resetting the mcpRuntime to its unavailable state upon store disposal to prevent stale references.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +20 to +39
const codingModes = [
{ value: 'ask', label: 'Ask', icon: 'i-solar:question-circle-bold-duotone', title: 'Ask mode' },
{ value: 'spec', label: 'Spec', icon: 'i-solar:clipboard-list-bold-duotone', title: 'Spec mode' },
{ value: 'code', label: 'Code', icon: 'i-solar:code-square-bold-duotone', title: 'Code mode' },
{ value: 'debug', label: 'Debug', icon: 'i-solar:bug-bold-duotone', title: 'Debug mode' },
] as const satisfies readonly {
value: CodingMode
label: string
icon: string
title: string
}[]

const specEntryPaths = [
{ value: 'requirements-first', label: 'Requirements' },
{ value: 'design-first', label: 'Design' },
{ value: 'quick-spec', label: 'Quick spec' },
] as const satisfies readonly {
value: SpecEntryPath
label: string
}[]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Several user-facing labels, titles, and placeholders in this component are hardcoded in English (e.g., 'Ask', 'Spec', 'Requirements', 'Workspace root'). To support internationalization, these strings should be defined in the localization files (e.g., packages/i18n/src/locales/en/settings.yaml) and retrieved using the t(...) translation helper.

Comment on lines +252 to +258
function collectDiagnostics(value: unknown): WorkspaceRendererRow[] {
const diagnostics = collectRows(value, ['diagnostics', 'items', 'results'], 6)
return diagnostics.map((row) => {
const severity = row.meta?.toLowerCase()
return severity ? row : row
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The ternary expression severity ? row : row is redundant as it returns row in both branches. It appears the intention was to map the severity to a corresponding tone (similar to the logic in diagnosticRows) so that the diagnostics renderer can style the rows correctly.

function collectDiagnostics(value: unknown): WorkspaceRendererRow[] {
  const diagnostics = collectRows(value, ['diagnostics', 'items', 'results'], 6)
  return diagnostics.map((row) => {
    const severity = row.meta?.toLowerCase()
    const tone: WorkspaceRendererRow['tone'] =
      severity === 'error' ? 'danger' : severity === 'warning' || severity === 'warn' ? 'warning' : 'neutral'
    return {
      ...row,
      tone,
    }
  })
}

Comment on lines +175 to +178
function dispose() {
codingContextEnabled.value = false
clearRegistrations()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When the store is disposed, mcpRuntime is not reset to unavailableMcpRuntime. This leaves a stale reference to the handlers of the disposed mcpToolsStore (such as listMcpToolsAndUpdateCodingBackend), which can prevent garbage collection and lead to unexpected behavior if tools are invoked after disposal.

  function dispose() {
    codingContextEnabled.value = false
    clearRegistrations()
    mcpRuntime.value = unavailableMcpRuntime
  }

- Adds missing opener/noreferrer to the Serena setup URL anchor to fix the
  target=_blank security finding (JS-0712).
- Extracts pickBestSerenaGroup and groupReadOnlySerenaTools from
  selectSerenaReadOnlyTools to reduce per-function cyclomatic complexity.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
return serenaTools
}

function groupReadOnlySerenaTools(tools: NormalizedMcpToolSummary[]): Map<string, NormalizedMcpToolSummary[]> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`groupReadOnlySerenaTools` has a cyclomatic complexity of 6 with "medium" risk


A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.

@vi70x4 vi70x4 merged commit 7821466 into main Jun 30, 2026
8 of 9 checks passed
@vi70x4 vi70x4 deleted the feat/coding-workspace branch June 30, 2026 22:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants