Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
5073329
fix(install): native-app installs read runtimeType from registry mani…
samxu01 May 23, 2026
df60a6e
docs(audits): 2026-05-23 v2 UI smoke walkthrough findings
samxu01 May 23, 2026
80cf647
docs(audits): add 4-runtime verification record (incl. clawdbot local…
samxu01 May 23, 2026
7fe513b
fix(v2): chip auto-sends + marketplace hits shipped /api/marketplace/…
samxu01 May 23, 2026
89d4844
docs(audits): add §3.7 fan-out verification + 4-path runtime status
samxu01 May 23, 2026
1f51b56
docs(audits): add real claude CLI wrapper to verified runtime matrix
samxu01 May 23, 2026
988a78c
docs(plans): sprint 2026-05-23 — local-dev parity + agent collab smoke
samxu01 May 23, 2026
6f89fd9
docs(audits): huddle observations T+7 min — 5 Commonly affordance gaps
samxu01 May 24, 2026
ff522eb
docs(audits): huddle T+22min — Cody found 3 P1/P2 bugs in PR #434
samxu01 May 24, 2026
6839eea
fix(v2): rewire marketplace installs through registry
May 24, 2026
885c54d
docs(audits): huddle T+37min — Cody shipped first agent commit 6839eea9
samxu01 May 24, 2026
722aa95
docs(audits): huddle T+52min — Nova delegated install.ts to sam-local…
samxu01 May 24, 2026
c50b061
docs(audits): huddle T+62min — Nova doubled down on delegation, Sam c…
samxu01 May 24, 2026
c97608a
docs(audits): huddle T+67min — principle landed, all 3 agents pivoted
samxu01 May 24, 2026
80533ef
docs(audits): huddle T+82min — ADR-2.F drafted, peer-reviewed in 60s
samxu01 May 24, 2026
807b539
fix(install): narrow manifest runtime fallback
May 24, 2026
d4dc25f
docs(audits): huddle T+97min — STOP CONDITION HIT, loop closed
samxu01 May 24, 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
194 changes: 194 additions & 0 deletions backend/__tests__/unit/routes/registry.install-runtime-type.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
jest.mock('../../../models/AgentRegistry', () => ({
AgentRegistry: {
getByName: jest.fn(),
incrementInstalls: jest.fn(),
},
AgentInstallation: {
findOne: jest.fn(),
find: jest.fn(),
install: jest.fn(),
},
}));

jest.mock('../../../models/Pod', () => ({
findById: jest.fn(),
}));

jest.mock('../../../models/User', () => ({
findOne: jest.fn(),
findById: jest.fn(),
}));

jest.mock('../../../models/AgentProfile', () => ({
findOneAndUpdate: jest.fn(),
}));

jest.mock('../../../models/Activity', () => ({
create: jest.fn(),
}));

jest.mock('../../../services/agentIdentityService', () => ({
buildAgentUsername: jest.fn((agentName, instanceId = 'default') => (
instanceId === 'default' ? agentName : `${agentName}-${instanceId}`
)),
getOrCreateAgentUser: jest.fn().mockResolvedValue({ _id: 'bot-1' }),
ensureAgentInPod: jest.fn().mockResolvedValue(true),
}));

jest.mock('../../../services/agentMessageService', () => ({
postMessage: jest.fn().mockResolvedValue(true),
}));

const { AgentRegistry, AgentInstallation } = require('../../../models/AgentRegistry');
const Pod = require('../../../models/Pod');
const User = require('../../../models/User');
const AgentProfile = require('../../../models/AgentProfile');
const Activity = require('../../../models/Activity');
const AgentIdentityService = require('../../../services/agentIdentityService');
const installRouter = require('../../../routes/registry/install');

const getInstallHandler = () => {
const layer = installRouter.stack.find((entry) => (
entry.route
&& entry.route.path === '/install'
&& entry.route.methods.post
));
if (!layer) {
throw new Error('Install route handler not found');
}
return layer.route.stack[layer.route.stack.length - 1].handle;
};

const buildLeanChain = (result) => ({
lean: jest.fn().mockResolvedValue(result),
});

const buildSelectLeanChain = (result) => ({
select: jest.fn().mockReturnValue({
lean: jest.fn().mockResolvedValue(result),
}),
});

describe('registry install runtimeType fallback', () => {
const installHandler = getInstallHandler();

beforeEach(() => {
jest.clearAllMocks();

Pod.findById.mockReturnValue(buildLeanChain({
_id: 'pod-1',
createdBy: 'user-1',
members: ['user-1'],
type: 'chat',
}));

AgentInstallation.findOne.mockResolvedValue(null);
AgentInstallation.find.mockReturnValue(buildLeanChain([]));
AgentInstallation.install.mockImplementation(async (_agentName, _podId, options) => ({
_id: { toString: () => 'install-1' },
agentName: 'sample-agent',
instanceId: options.instanceId || 'default',
displayName: options.displayName || 'Sample Agent',
version: options.version,
status: 'active',
scopes: options.scopes || [],
}));

AgentRegistry.incrementInstalls.mockResolvedValue({ acknowledged: true });

User.findOne.mockImplementation(() => buildSelectLeanChain(null));
User.findById.mockReturnValue(buildSelectLeanChain({ username: 'installer' }));

AgentProfile.findOneAndUpdate.mockResolvedValue(true);
Activity.create.mockResolvedValue(true);
});

it('copies manifest.runtime.runtimeType into the installation when the caller omits runtimeType', async () => {
AgentRegistry.getByName.mockResolvedValue({
agentName: 'sample-agent',
displayName: 'Sample Agent',
description: 'Native first-party app',
latestVersion: '1.0.0',
manifest: {
context: { required: [] },
runtime: {
type: 'standalone',
runtimeType: 'native',
},
},
});

const req = {
body: {
agentName: 'sample-agent',
podId: 'pod-1',
version: '1.0.0',
config: {},
scopes: [],
},
user: { id: 'user-1', username: 'installer' },
userId: 'user-1',
};
const res = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
};

await installHandler(req, res);

expect(AgentInstallation.install).toHaveBeenCalledWith(
'sample-agent',
'pod-1',
expect.objectContaining({
config: expect.objectContaining({
runtime: expect.objectContaining({
runtimeType: 'native',
}),
}),
}),
);
expect(res.status).not.toHaveBeenCalledWith(500);
});

it('does not copy manifest.runtime.type deployment metadata into runtimeType', async () => {
AgentRegistry.getByName.mockResolvedValue({
agentName: 'sample-agent',
displayName: 'Sample Agent',
description: 'Community marketplace app',
latestVersion: '1.0.0',
manifest: {
context: { required: [] },
runtime: {
type: 'standalone',
},
},
});

const req = {
body: {
agentName: 'sample-agent',
podId: 'pod-1',
version: '1.0.0',
config: {},
scopes: [],
},
user: { id: 'user-1', username: 'installer' },
userId: 'user-1',
};
const res = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
};

await installHandler(req, res);

expect(AgentInstallation.install).toHaveBeenCalledWith(
'sample-agent',
'pod-1',
expect.objectContaining({
config: {},
}),
);
expect(res.status).not.toHaveBeenCalledWith(500);
});
});
18 changes: 18 additions & 0 deletions backend/routes/registry/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,24 @@ installRouter.post('/install', installRateLimit, auth, async (req: any, res: any
resolvedGateway = await resolveGatewayForRequest({ gatewayId, userId });
runtimeConfig.gatewayId = resolvedGateway._id.toString();
}
// Fall back to the registry manifest's declared runtimeType when the caller
// didn't pick one. Without this, native first-party apps installed via the
// v2 UI land with runtimeType=null → events route to the external queue
// (which has no listener for native apps) → agent never replies. Only copy
// the dedicated runtime identity field: `manifest.runtime.type` is
// deployment-shape metadata (`standalone` / `commonly-hosted` / `hybrid`),
// not the install row's canonical driver identity.
if (!runtimeConfig.runtimeType) {
const manifestRuntimeType = String(
(agent.manifest as any)?.runtime?.runtimeType || '',
).trim().toLowerCase();
if (
manifestRuntimeType
&& !['standalone', 'commonly-hosted', 'hybrid'].includes(manifestRuntimeType)
) {
runtimeConfig.runtimeType = manifestRuntimeType;
}
}
if (Object.keys(runtimeConfig).length) {
installConfig.runtime = runtimeConfig;
}
Expand Down
2 changes: 2 additions & 0 deletions backend/scripts/seed-native-agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ async function seedOneApp(app: NativeAgentDefinition): Promise<void> {
name: app.agentName,
version: VERSION,
description: app.description,
runtime: { type: 'native', runtimeType: 'native' },
},
latestVersion: VERSION,
},
Expand All @@ -188,6 +189,7 @@ async function seedOneApp(app: NativeAgentDefinition): Promise<void> {
name: app.agentName,
version: VERSION,
description: app.description,
runtime: { type: 'native', runtimeType: 'native' },
},
publishedAt: new Date(),
},
Expand Down
4 changes: 4 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ services:
# LLM API keys (optional if using Claude Code OAuth)
- GEMINI_API_KEY=${GEMINI_API_KEY}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- OPENAI_API_KEY=${OPENAI_API_KEY}
- OPENAI_BASE_URL=${OPENAI_BASE_URL}
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
- OPENROUTER_BASE_URL=${OPENROUTER_BASE_URL}
volumes:
- ./external/clawdbot-state/config:/home/node/.clawdbot
- ./external/clawdbot-state/workspace:/home/node/clawd
Expand Down
84 changes: 84 additions & 0 deletions docs/audits/ui-smoke-2026-05-23/FINDINGS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# V2 UI smoke + gap analysis — findings (2026-05-23)

**Worktree**: `.claude/worktrees/ui-smoke-2026-05-23` off `main@e89670a5`.
**Stack**: `./dev.sh up` local Docker Compose + kubectl port-forward to dev cluster's LiteLLM.

## Capsule

- **Local deploy path works** end-to-end now that the native-runtime install bug is fixed and LiteLLM creds are wired (PR #434 just landed for the install bug).
- **V2 shell core** (login → pod → chat → composer) is polished and clean — 0 console errors on the happy path.
- **V2 nav rail has 4 tabs (Pods, Agents, Apps, Settings)**. Of those, Pods + Agents are well-built v2-native. **Apps (=Marketplace) and Settings are legacy MUI components mounted under v2** and are real gap surfaces.
- **Default post-login route is `/feed` (legacy)**, not `/v2`. New users don't see v2 until they manually navigate.
- **Mobile breakpoint broken below ~1100px** (memory confirmed in walkthrough; inspector fills viewport, layout collapses).
- **Two agent-runtime adapter paths verified locally**: native in-process and CLI-wrapper polling.

## Detailed findings

### Bugs

| # | Severity | Where | Fix |
|---|---|---|---|
| F1 | P0 | `/api/registry/install` writes `runtime={}` for native first-party apps installed via UI — events route to external queue, agent never replies | **PR #434 shipped** (registry-manifest fallback for runtimeType) |
| F2 | P0 | V2 marketplace (`/v2/marketplace`) calls `/api/apps/marketplace*` (legacy shadows) instead of `/api/marketplace/browse` — Discover and Installed counts are 0 even after installs | (separate PR; backend already shipped via #215/#230) |
| F3 | P1 | Default post-login route is `/feed` (legacy) | router change in `App.tsx` |
| F4 | P1 | Chip click in agent-room empty-state fills composer but doesn't auto-send | add `submit` to chip click handler |
| F5 | P1 | V2 Settings has only 3 tabs (Overview / Apps / API Token) — no Account-security, no Pod settings, no Admin sub-page | redesign per `settings-v2-gaps.md` |
| F6 | P1 | "Apps Marketplace" link inside Agent Hub goes to `/apps` (legacy) | update to `/v2/marketplace` |
| F7 | P1 | Mobile breakpoint broken below ~1100px (memory: `project-v2-mobile-not-responsive`) | responsive sprint; deferred per ADR-011 |
| F8 | P2 | Apps tab label vs Marketplace heading mismatch | pick one |

### Surface inventory (TL;DR)

| V2 surface | Status |
|---|---|
| Pods + chat + composer | ✅ Polished, v2-native |
| Agents (Your Team / Hire) | ✅ Polished, v2-native |
| Agent install + agent-room | ✅ Polished, v2-native |
| Marketplace (`/v2/marketplace`) | ❌ Legacy MUI, wrong endpoints |
| Settings (`/v2/settings`) | ⚠️ Legacy MUI wrapped, account-only |
| Landing (`/`) | ⚠️ Legacy, dark/gradient theme; v2 design proposal drafted (see `landing-v2-proposal.md`) |

### Subagent gap audits (separate files)

- `marketplace-v2-gaps.md` — endpoint map + 2-3 PR redesign plan
- `settings-v2-gaps.md` — surface inventory + minimal v2 hub proposal
- `landing-v2-proposal.md` — v2 landing design (hero ASCII mock, sections, implementation footprint)
- `local-agent-runtimes-verified.md` — recipes for native + CLI-wrapper paths
- `walkthrough-2026-05-23.md` — beat-by-beat UI walk

### Agent-runtime paths verified

1. **Native (in-process)** — All 3 first-party apps (pod-welcomer, task-clerk, pod-summarizer) reply via `nativeRuntimeService.runAgent` → LiteLLM → reply ~3-8s. Unlocked by PR #434.
2. **CLI-wrapper stub** — `commonly agent attach stub` + `agent run` echo reply.
3. **CLI-wrapper real codex CLI 0.133.0 in tmux** — codex talks to LiteLLM via `~/.codex/config.toml`, "REAL_CODEX_OK" reply. Real adapter path, no OAuth.
4. **OpenClaw clawdbot-gateway local** — infrastructure verified end-to-end (token chain, Docker build, gateway running in tmux, WebSocket connected, chat.mention events delivered to moltbot). LLM call sub-step blocked on openclaw auth-profile schema mystery — see `local-agent-runtimes-verified.md` for the three shapes attempted.

### Agent-DM §3.7 fan-out verified (2026-05-23 loop tick)

`POST /api/agents/runtime/agent-dm` with cuz-local's runtime token + `{"target":{"agentName":"pod-welcomer","instanceId":"default"}}` → backend creates `Cuz Local ↔ Pod Welcomer` agent-dm pod (type=`agent-dm`, exactly 2 members per ADR-001 §3.10). Smoke-admin (who shares Smoke Test Pod with both cuz-local + pod-welcomer) can `GET /api/pods/<DM_POD>` and read name/type/member-count — confirming the §3.7 carve-out (PR #381) that lets humans navigate to a2a DMs they're related to via shared-pod membership.

### What didn't get verified

- **OpenClaw moltbot LLM call** — infrastructure ✅, LLM auth quirk open (separate follow-up task).
- **Real `claude` CLI adapter** — `claude` is installed (Claude Code 2.1.150) but not attached as a Commonly agent in this session; codex covers the wrapper pattern.

## Recommended next sprint (post-this-session)

P0 first:
1. **PR for F2** — rewire `/v2/marketplace` to call `/api/marketplace/browse` and friends. Add detail page `/v2/marketplace/:id`. Token-align with v2.css. ~3-4 days per subagent recommendation.
2. **PR for F3** — change default post-login route to `/v2`. One-line router change.

P1 batch:
3. **PR for F4** — chip click should send. One-line composer change.
4. **PR for F6** — fix "Apps Marketplace" cross-link in Agent Hub.

Bigger landings:
5. **V2 Settings hub** per `settings-v2-gaps.md` — Phase 1 (Account security + My Pods member mgmt), ~2-3 days.
6. **V2 Landing** per `landing-v2-proposal.md` — ~700 LOC, 1 PR.
7. **Mobile responsive** — separate sprint per ADR-011 cadence.

## Knowledge-base updates ready to ship

- **Memory entry**: `project-2026-05-23-v2-ui-smoke.md` — sprint outcome + PR #434 fix + the four audit docs as pointer artifacts.
- **No new prescriptive rule** surfaced (the install fix is shipped as code; the audit gaps are roadmap items, not rules).
- **No new skill** needed; existing `frontend-dev`, `agent-runtime`, `installable-taxonomy` skills already cover the surface.
Loading
Loading