Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ new version heading in the same commit.

## [Unreleased]

## [0.187.0] — 2026-07-14
### Fixed
- **The GitHub "Install the App" button/link now shows for hand-configured Apps, regardless of install
state.** The App slug (needed to build the `github.com/apps/<slug>/installations/new` link) is resolved
from `GET /app` whenever the bot creds are present — not only in the authorized-but-not-installed case.
So the **Connections → Creds → GitHub** admin install button appears even when the App is already
installed (for adding more orgs/repos), and it self-heals on any admin Creds view / member GitHub-panel
view / bot-creds save — not just when a member is stuck uninstalled. (`src/server.ts` — resolve in
`GET/PUT /api/settings/integrations` + ungate `/api/github/me`; `scripts/github-per-member-test.cjs` now 76/76.)

## [0.186.1] — 2026-07-14
### Changed
- **Sharpened the `engineer` ↔ `ops` boundary** so the two generalists stop overlapping. Both used to say
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agent-os",
"version": "0.186.1",
"version": "0.187.0",
"description": "A generic, governed operating system for running autonomous agents safely across brands. Ships with a local web console.",
"license": "MIT",
"type": "commonjs",
Expand Down
8 changes: 8 additions & 0 deletions scripts/github-per-member-test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,15 @@ async function main() {
const meNI2 = await (await call('GET', '/api/github/me')).json();
assert(meNI2.install && meNI2.install.installed === false && (meNI2.installUrl || '').includes('/apps/agent-os-instapods/installations/new'),
'/me self-heals the App slug → a real install link when authorized-but-not-installed');
// Resolves even when ALREADY installed (so the admin "install on more repos" button is available too).
osx.settings.setGithubAppSlug('', 'owner@test');
installState = 'installed';
const meInst = await (await call('GET', '/api/github/me')).json();
assert((meInst.installUrl || '').includes('/apps/agent-os-instapods/installations/new'), 'App slug resolves regardless of install state');
// And the admin Creds view (GET /api/settings/integrations) self-heals + exposes the install button.
osx.settings.setGithubAppSlug('', 'owner@test');
const iv2 = await (await call('GET', '/api/settings/integrations')).json();
assert(iv2.github && (iv2.github.installUrl || '').includes('/apps/agent-os-instapods/installations/new'), 'Creds view resolves the slug → install button URL');
gid2.clear(ownerId);
// Removing the private key drops the cached bot token + resolved installation.
gid2.setPrivateKey('', 'owner@test');
Expand Down
11 changes: 9 additions & 2 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3165,6 +3165,10 @@ async function handle(os: AgentOS, tm: TerminalManager, autos: Automations, req:
// ── integration credentials (Composio now; Slack app etc. later) — instance-wide secrets ──
if (method === 'GET' && p === '/api/settings/integrations') {
if (!isAdmin(me)) return sendJson(res, 403, { error: 'owner or admin required' });
// Self-heal the App slug (→ the Creds "Install the App" button) for a hand-configured App that never
// got one from the manifest flow. Resolves once from GET /app whenever the bot creds are present.
const ghv = new GithubIdentity(os);
if (ghv.botConfigured() && !ghv.appSlug()) await ghv.ensureAppSlug(me.email).catch(() => { /* best-effort */ });
return sendJson(res, 200, integrationsView(os));
}
// Live Atlas catalog for the default-model pickers (dropdown + free text). Fetched with the stored
Expand Down Expand Up @@ -3223,6 +3227,8 @@ async function handle(os: AgentOS, tm: TerminalManager, autos: Automations, req:
const ghb = new GithubIdentity(os);
if (ghb.botConfigured()) {
const bot = await ghb.ensureBotToken(Date.now(), me.email).catch(() => undefined);
// Also resolve the App slug now → the "Install the App" link/button (a hand-set App has no slug yet).
if (!ghb.appSlug()) await ghb.ensureAppSlug(me.email).catch(() => { /* best-effort */ });
os.audit.append({ ts: Date.now(), runId: '-', tenant: os.tenant, principal: me.email, type: bot ? 'github.bot_token.minted' : 'github.bot_token.failed', data: { installationId: os.settings.githubInstallationId() || null } });
}
}
Expand Down Expand Up @@ -3267,8 +3273,9 @@ async function handle(os: AgentOS, tm: TerminalManager, autos: Automations, req:
if (st && !('error' in st)) install = st;
}
// Self-heal the install link: a hand-configured App has no slug from the manifest flow, so resolve it
// once (from GET /app) when we can, so the "Install the App" prompt is a real link, not just text.
if (blob && install && !install.installed && gh.botConfigured() && !gh.appSlug()) {
// once (from GET /app) whenever the bot creds are present — regardless of install state, so the link is
// ready both for the not-installed warning AND the admin's "install on more repos" button.
if (gh.botConfigured() && !gh.appSlug()) {
await gh.ensureAppSlug(me.email).catch(() => { /* best-effort; the text guidance still shows */ });
}
return sendJson(res, 200, { configured: gh.configured(), connected: !!blob, login: blob?.login, expiresAt: blob?.expiresAt, install, installUrl: gh.installUrl() });
Expand Down
Loading