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
4 changes: 3 additions & 1 deletion agent_host/adapters/pi.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,9 @@ async def _event(self, run_id, run, event):
elif event.get("method") == "notify" and str(event.get("message") or "").strip():
notice = redact_mcp_text(str(event["message"]), self.mcp_connections)
notice = notice.replace("Run /mcp to fix.",
"Review connections in Settings -> Providers -> MCP, then restart the backend.")
"Review installed package connections in Plugins, or manual connections in "
"Settings -> Providers -> MCP. Use Test connection to diagnose the service, "
"then restart the backend after changing saved connection settings.")
await self._emit(run_id, run, "runtime.notice", {
"message": notice, "level": str(event.get("notifyType") or "info"),
"source": "pi_native_notification"})
Expand Down
10 changes: 9 additions & 1 deletion agent_host/mcp_connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ def public_dict(self) -> dict[str, Any]:
"bearer_token_env_var": self.bearer_token_env_var,
"environment_keys": sorted(self.environment),
"main_chat_access": False,
**({"extension_package_id": self.extension_package_id} if self.extension_package_id else {}),
}


Expand Down Expand Up @@ -361,7 +362,14 @@ async def open_mcp_connection(
if not token:
raise RuntimeError("configured bearer token is unavailable")
headers["Authorization"] = f"Bearer {token}"
async with AsyncClient(headers=headers) as http_client:
async def preserve_authentication_error(response):
# The SDK maps non-JSON-RPC failures to a generic protocol error. Keep
# HTTP authentication facts at the transport boundary; negotiation's
# other error statuses must still reach the native MCP client.
if response.status_code in (401, 403):
response.raise_for_status()

async with AsyncClient(headers=headers, event_hooks={"response": [preserve_authentication_error]}) as http_client:
transport = streamable_http_client(connection.url, http_client=http_client)
async with Client(transport, raise_exceptions=False) as client:
yield client
107 changes: 107 additions & 0 deletions docs/verification/extensions/gui-runtime-0.0.8-live-2026-11-05.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Windows GUI Runtime Connector 0.0.8 independent live acceptance

The bounded installed-Pi GUI journey passed with connector **0.0.8** and Runtime
**0.1.11** on 2026-11-05 (Pacific/Auckland). This closes the outstanding four-step
GUI, original-image review, same-session observe/continue and maintenance-settlement
checks. It is evidence for the exact combination, not catalog approval or publication.

Host connection diagnostics were tested with the local fixes accompanying this
record, based on `ffb1037b6a4cc8bcbf768d51111b108361952cb0`. Those fixes must be
reviewed and integrated before describing the diagnostic experience as shipped.

## Tested bytes and environment

- Submission: [Amadeus-Extensions #11](https://github.com/Code-Amadeus/Amadeus-Extensions/pull/11),
head `8705a3f04e602d145d74782c310f6866cbc20f24`, still open when checked.
- Wheel: `amadeus_agent_gui_runtime-0.0.8-py3-none-any.whl`, 27,990 bytes,
SHA-256 `2f532faf152faf2e1e3ab3140e1097487689be34d638e4ee86539e06886cbcaf`.
- Runtime: the existing installation was reused at unmodified tracked source
`323099100b04beeb6ee67a846f0e530998c19bbc`; no second installation was created.
- Windows, isolated Host profile and test directories, software-rendered Electron.
Pi used the configured `openai-codex/gpt-6-luna` subscription. Runtime grounding
and decision calls used the configured external Luna API; those are a separate,
potentially billable connection. No provider or input gate was bypassed.
- The previously completed 2026-10-29 distribution/static and HTTP-owner lifecycle
review remains supporting evidence at its original scope. This run does not
reclassify those checks as GUI evidence or qualify arbitrary Runtime versions.

## Observed live results

| Check | Observed result |
| --- | --- |
| Four-step GUI task | Documents → Reports → Up once → Receipts completed in one plan, with four original navigation executions. The original final image shows Receipts and its marker file. |
| Original-image review | The first step deliberately required Main's post-action judgment. Pi retrieved the bound original after image, made one read-only `observe`, reviewed the current evidence packet and continued the original plan once. No input replay or recovery tool was used. |
| Evidence integrity | All 28 file references in the four-step history and all four original after-image digests matched retained bytes. Both fixture marker hashes and the file inventory were unchanged. |
| Operator-owned window | The original 7-Zip window and process remained open after Runtime cleanup, at Receipts. A restricted process lookup initially hid it; native window inspection and an unrestricted process-identity check confirmed preservation. |
| Session cleanup | The session report records `phase=stopped`, `host_phase=stopped`, no cleanup errors and a stopped sampler. Pi's final status reported no pending requests and verified cleanup. |
| Official maintenance settle | The unmodified official updater returned `settled=true`, `registered_sessions=5`, `cleared_sessions=5`. After the remaining diagnostic/install lifecycle, a second check returned 0/0. Coverage is registered sessions only; legacy sessions were not proven. |
| Missing service | The real Plugins → Test connection action displayed the service/URL/port diagnosis. A fresh Pi Work launched through the actual chat UI showed a native `fetch failed` warning with the Plugins / Settings → Providers → MCP and Test connection directions. |
| Wrong token | The real Plugins connection test displayed HTTP 401 and a token configuration hint. The earlier independent Pi attempt exposed native authentication-required failure; the final warning wording and credential redaction also passed regression tests. |
| Disable while service runs | Native disable removed the projected connection after Host restart; the separately running Runtime retained the same process identity. |
| Uninstall/reinstall | After the Host/backend stopped, the real installer removed the exact connector with `deleteData=false`, then installed/enabled the same wheel again using native secret storage. The independent Runtime remained running across removal. |

The first verification used a supplied, valid four-step v3 plan and a deliberate
manual post-action wait. The other three fixture conditions used the documented
decision-profile configuration. This proves the review/observe/continue contract;
it is not a sample of spontaneous double-rejection frequency or general GUI accuracy.

Earlier unsuccessful attempts are retained: screen occlusion, a Computer Use cursor
overlay, interrupted test processes, and an incorrect test-plan review setting.
The latter was corrected in the test input, not Runtime code. The successful run
is not presented as an error-free first attempt. Per-tool approvals were resolved
through the Host's normal permission API; status alone received a Work-scoped grant.

The execution host completed its native cleanup. The external HTTP daemon was
then terminated by the test controller, with exit code 1; this is not evidence of
graceful daemon shutdown. The isolated Electron Host later closed normally with
exit code 0. Test processes were stopped; the operator's 7-Zip window was retained.

## Host repairs and validation

The installed-plugin card now tests its actual Host-issued MCP connection ID.
The existing public connection projection includes its package owner so the UI
does not reconstruct IDs. HTTP authentication failures retain their transport
status through the MCP client; 401, 403 and unreachable-service diagnoses give
actionable, credential-free guidance. Other statuses retain native MCP negotiation.
Pending configuration cannot probe an obsolete connection. A cancelled settings
refresh cannot leave a stale reconnecting error after a successful restart.

- `python -m pytest -q tests/test_mcp_connections.py tests/test_extension_mcp.py tests/test_pi_provider.py`:
**45 passed**. Includes native HTTP negotiation, authentication/unreachable
diagnostics, projection isolation and native Pi notification redaction.
- `npm --prefix electron run build`: passed.
- `electron tests/extensionSettings.smoke.mjs --disable-gpu`: **14 checks passed**,
including connection results, unapplied settings and stale-refresh recovery.
- Ruff on the five changed Python files and `git diff --check`: passed.
- Full repository CI remains a requirement for merging the Host changes; this
record does not substitute the targeted checks for that CI.

Raw evidence remains local because it includes screenshots, process metadata and
test-profile details. The ignored audit directory
`.amadeus-test/gui-runtime-008-live-20261105/` retains `final-gui-evidence.json`,
`official-settle-final.json`, `official-settle-post-lifecycle.json`, the original
Runtime receipts/images, UI text observations and native installer receipts.
The GUI evidence collector verifies hashes without executing the package.

## Remaining publication gates

No new connector byte defect was established by this run; these results do not
justify requesting another connector version. The next owner is the maintainer:

1. Integrate the tested Host diagnostic fixes and complete their required CI.
2. Complete the full curated-v1 artifact/ownership/rights review record, then add
the exact 0.0.8 `catalog/packages/` input to the same submission PR.
3. Obtain eligible non-author approval on the final head before merging inputs.
4. Resolve the repository-governance and release gates, then use a separate index
publication PR and verify the official served bytes.

Candidate-only admission still does not establish approval. This execution added
no catalog approval, changed no repository protections and published no index.

## 中文结论

0.0.8 + 0.1.11 的四步 GUI、原图审核、observe/continue、窗口保留、文件不变、
收尾和官方 settle 已通过独立实机验收;禁用、卸载和重装也确认了外部 Runtime
由用户独立管理的边界。连接诊断在带本轮修复的 Host 上通过,不能提前称为已发布功能。
不需要因这轮结果要求作者再发一个版本。正式收录、最终非作者批准、治理检查和独立
发布 PR 仍须完成,实机通过不等于已经上架。
32 changes: 31 additions & 1 deletion electron/src/renderer/components/ExtensionSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { compatibleExtensionProviders, extensionDraft, extensionLanguageSupporte
const ATTENTION_STATES = new Set(['error', 'degraded', 'needs_config', 'incompatible'])

interface Props {
mcpConnections: Array<{ id: string; name: string; extension_package_id?: string }>
send: (method: string, params?: Record<string, unknown>) => Promise<Record<string, unknown>>
platform?: string
extensions: ExtensionSummary[]
runtime: ExtensionRuntimeStatus[]
Expand All @@ -34,7 +36,7 @@ interface Props {
onEditorStateChange?: (state: SettingsEditorState) => void
}

export default function ExtensionSettings({ platform, extensions, runtime, providers, connected, available, encryptionAvailable, selectedVoiceBackend, selectedTheme, appliedThemeVersion, onOpenAppearance,
export default function ExtensionSettings({ platform, extensions, runtime, mcpConnections, send, providers, connected, available, encryptionAvailable, selectedVoiceBackend, selectedTheme, appliedThemeVersion, onOpenAppearance,
onSettingsChanged, onRestartRequired, onOpenVoice, onEditorStateChange }: Props) {
const { locale, t } = useI18n()
const scheduledUpdates = usesScheduledExtensionUpdates(platform)
Expand Down Expand Up @@ -441,6 +443,9 @@ export default function ExtensionSettings({ platform, extensions, runtime, provi
</div>
</li>)}
</ul>
{connected && !pending ? mcpConnections.filter(connection => connection.extension_package_id === extension.id).map(connection =>
<ExtensionConnectionTest key={`${connection.id}:${extension.appliedRevision}`} connection={connection} send={send} disabled={busy || dirty} />
) : null}
<div className="extension-card-bottom">
<details className="extension-details">
<summary>{t('Plugin details')}</summary>
Expand Down Expand Up @@ -495,3 +500,28 @@ export default function ExtensionSettings({ platform, extensions, runtime, provi
</section>
</div>
}

function ExtensionConnectionTest({ connection, send, disabled }: {
connection: { id: string; name: string }; send: Props['send']; disabled: boolean
}) {
const { t } = useI18n()
const [running, setRunning] = useState(false)
const [result, setResult] = useState<{ message: string; ok: boolean } | null>(null)
const test = async () => {
setRunning(true); setResult(null)
try {
const response = await send('mcp.connection.test', { connection_id: connection.id })
setResult({ ok: response.status === 'connected', message: response.status === 'connected'
? t('Connected · {count} tools discovered', { count: Number(response.tool_count || 0) })
: String(response.detail || response.code || 'Connection failed').split('\n').map(line => t(line)).join('\n') })
} catch (reason) {
setResult({ ok: false, message: reason instanceof Error ? reason.message : t('Connection failed') })
} finally { setRunning(false) }
}
return <div className="settings-embedded-stack">
<div className="extension-contribution-heading"><span>{connection.name}</span>
<SettingsButton disabled={disabled || running} onClick={() => void test()}>{t(running ? 'Connecting…' : 'Test connection')}</SettingsButton>
</div>
{result ? <div role="status" className={result.ok ? 'settings-field-description' : 'settings-form-error'}>{result.message}</div> : null}
</div>
}
18 changes: 12 additions & 6 deletions electron/src/renderer/components/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,7 @@ export default function SettingsPage({ send, subscribe, connected, reconnectBack
const [acpAgents, setAcpAgents] = useState('[]')
const [acpConfigurations, setAcpConfigurations] = useState<AcpConfiguration[]>([])
const [capabilityPackages, setCapabilityPackages] = useState<CapabilityPackage[]>([])
const [runtimeMcpConnections, setRuntimeMcpConnections] = useState<Array<{ id: string; name: string; extension_package_id?: string }>>([])
const [desktop, setDesktop] = useState<DesktopSettingsSnapshot | null>(null)
const [companionPortraits, setCompanionPortraits] = useState<CompanionPortraitStatus | null>(null)
const [saving, setSaving] = useState<string | null>(null)
Expand All @@ -701,10 +702,11 @@ export default function SettingsPage({ send, subscribe, connected, reconnectBack
}, [])

const refreshBackend = useCallback(async () => {
const [configResponse, providerResponse, capabilityResponse] = await Promise.all([
const [configResponse, providerResponse, capabilityResponse, mcpResponse] = await Promise.all([
send('system.get_config', {}),
send('provider.list', {}),
send('capability.list', { include_disabled: true }),
send('mcp.connection.list', {}),
])
setConfig((configResponse.values as Record<string, unknown>) ?? configResponse)
setAcpAgents(JSON.stringify(providerResponse.acp_agents || []))
Expand All @@ -713,18 +715,21 @@ export default function SettingsPage({ send, subscribe, connected, reconnectBack
setProviderManifests(Array.isArray(providerResponse.provider_manifests) ? providerResponse.provider_manifests as unknown as ProviderManifest[] : [])
setProviderRoleCandidates(providerResponse.role_candidates as Record<string, string[]> | undefined)
setCapabilityPackages(Array.isArray(capabilityResponse.packages) ? capabilityResponse.packages as unknown as CapabilityPackage[] : [])
setRuntimeMcpConnections(Array.isArray(mcpResponse.connections) ? mcpResponse.connections as Array<{ id: string; name: string; extension_package_id?: string }> : [])
}, [send])

useEffect(() => {
let active = true
if (restarting) return undefined
void Promise.all([refreshDesktop(), refreshCompanionPortraits()])
.catch(reason => setError(reason instanceof Error ? reason.message : 'Could not load desktop settings'))
if (!connected) return undefined
.catch(reason => { if (active) setError(reason instanceof Error ? reason.message : 'Could not load desktop settings') })
if (!connected) { setRuntimeMcpConnections([]); return () => { active = false } }
const unsubscribe = subscribe('system.config', payload => {
setConfig((payload.values as Record<string, unknown>) ?? payload)
})
void refreshBackend().catch(reason => setError(reason instanceof Error ? reason.message : 'Could not load runtime settings'))
return unsubscribe
}, [connected, subscribe, refreshBackend, refreshDesktop, refreshCompanionPortraits])
void refreshBackend().catch(reason => { if (active) setError(reason instanceof Error ? reason.message : 'Could not load runtime settings') })
return () => { active = false; unsubscribe() }
}, [connected, restarting, subscribe, refreshBackend, refreshDesktop, refreshCompanionPortraits])

useEffect(() => {
setRestartPending(Boolean(desktop?.restartRequired))
Expand Down Expand Up @@ -1313,6 +1318,7 @@ export default function SettingsPage({ send, subscribe, connected, reconnectBack
{section === 'extensions' ? <>
{(desktop?.extensionErrors || []).map((detail, index) => <div key={index} role="alert" className="settings-form-error">{t(detail)}</div>)}
<ExtensionSettings platform={desktop?.platform} extensions={desktop?.extensions || []} runtime={connected && Array.isArray(config.extensions) ? config.extensions as ExtensionRuntimeStatus[] : []}
mcpConnections={connected && !restarting ? runtimeMcpConnections : []} send={send}
providers={providerManifests} connected={connected} available={Boolean(desktop && window.amadeus?.installExtension)} encryptionAvailable={Boolean(desktop?.encryptionAvailable)}
selectedVoiceBackend={String(speechBackend?.value || '')} selectedTheme={selectedTheme} appliedThemeVersion={themeVersion}
onOpenAppearance={() => presentation === 'plugins' ? onOpenSettings?.('general') : setSection('general')}
Expand Down
Loading
Loading