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
8 changes: 7 additions & 1 deletion docs/system-specs/modules/governance.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,13 @@ pins ride in the policy file for it:
never run (`_shell_exec_args` returns `None`), so a Windows host with a
provider never updates from the gateway. A packaged desktop app with a provider is
treated as policy-managed: `_arm_packaged_app` refuses with "updates on this
host are managed by policy".
host are managed by policy". When the provider can apply on the current platform
(`can_apply` true, i.e. a trusted shell is available), the Electron desktop
updater's feed lane also stands down: it emits `not-available` and does not
contact the feed, so the provider is the one lane that finds and applies
updates. On Windows (`_shell_exec_args` returns `None`, `can_apply` false) the
provider runs nothing and the Electron updater continues as the working update
path.
- **`platform_commands`** — per-platform overrides of those two commands, keyed
`{sys.platform}-{machine}` (`linux-x86_64`, `darwin-arm64`, `win32-x86_64`;
an unrecognised machine keeps its raw lowercased name). A matching entry
Expand Down
2 changes: 1 addition & 1 deletion src/kiro_crew/docs/settings-registry.generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"label": "Update the gateway automatically",
"tab": "about",
"route": "/settings/about?highlight=key%3Aauto_update",
"description": "Installs a new release once no work is running and restarts the gateway, on installs that can update themselves.",
"description": "Restarts the gateway to install a new release once no work is running. When off, it stays on its current version.",
"configKey": "auto_update"
},
{
Expand Down
11 changes: 11 additions & 0 deletions website/capture/about-policy-managed.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>About policy-managed notice capture</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/capture/about-policy-managed.tsx"></script>
</body>
</html>
112 changes: 112 additions & 0 deletions website/capture/about-policy-managed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* Isolated capture entry for the About panel's "managed by policy" notice
* (GH #15797).
*
* WHY ISOLATED: the notice requires (a) `window.updateAPI` (Electron shell)
* and (b) a gateway status with `update_bundled_by_app: true`,
* `update_managed_by: 'command'`, and `update_can_apply: true`. That
* three-way combination only exists on a real desktop install with a policy
* provider that can run on the current platform. The shell and gateway status
* are stubbed; everything else is the real AboutPanel and stylesheet.
*
* Scenes (?scene=):
* policy bundled install + policy provider can apply → notice replaces
* the update card; AppAutoDownloadSwitch is hidden.
* normal bundled install, no policy provider (managed_by: 'electron') →
* the regular desktop update row; no notice. The comparison frame.
* Theme: &theme=dark|light
*/
import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { Provider } from 'react-redux'
import { MemoryRouter } from 'react-router-dom'

import { initI18n } from '../src/i18n'
import { store } from '../src/store'
import { sseStatus } from '../src/store/dashboardSlice'
import { AboutPanel } from '../src/pages/settings/AboutPanel'
import '../src/index.css'

initI18n('en')

const params = new URLSearchParams(location.search)
const scene = params.get('scene') === 'normal' ? 'normal' : params.get('scene') === 'reused-gateway' ? 'reused-gateway' : 'policy'
document.documentElement.setAttribute('data-theme', params.get('theme') || 'dark')

const noop = async () => ({ ok: true })
;(window as unknown as { updateAPI?: unknown }).updateAPI = {
onState: () => () => {},
check: noop,
download: noop,
install: noop,
getInfo: async () => ({
version: '0.8.0',
channel: 'stable',
stampedChannel: 'stable',
channelSwitchable: false, // policy-managed install has no channel switcher
channelPreference: 'stable',
platform: 'darwin-arm64',
packaged: true,
}),
}

const baseStatus = {
uptime: '1d', sessions: 1, messages: 0, cron_jobs: 0, subagents: 0, lessons: 0,
version: '0.8.0', version_display: '0.8.0',
update_bundled_by_app: true,
update_check_status: 'succeeded',
update_latest_version: '0.8.1',
update_available: true,
} as const

if (scene === 'policy') {
store.dispatch(sseStatus({
...baseStatus,
update_managed_by: 'command',
update_can_apply: true,
// 'notify' keeps the switch hidden in the policy scene: the switch
// is now intentionally hidden when policy governs, so this matches
// the real-code behavior and avoids an error state in the stub.
update_auto_effect: 'notify',
} as never))
} else if (scene === 'reused-gateway') {
// Topology 2: desktop app connected to a reused (non-bundled) CLI-service or
// SSH-tunnel gateway. bundledByApp=false makes updateSwitchesShown render the
// gateway auto-update switch regardless of effect.
store.dispatch(sseStatus({
...baseStatus,
update_bundled_by_app: false,
update_managed_by: 'electron',
update_can_apply: false,
update_auto_effect: 'install',
} as never))
} else {
store.dispatch(sseStatus({
...baseStatus,
update_managed_by: 'electron',
update_can_apply: false,
update_auto_effect: 'unknown',
} as never))
}

// For the reused-gateway scene, pre-populate the react-query cache so the
// GatewayAutoUpdateSwitch shows a working state rather than the
// "couldn't load" error state that appears when no gateway is present.
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } })
if (scene === 'reused-gateway') {
qc.setQueryData(['kirocrewConfig'], { auto_update: true })
}

createRoot(document.getElementById('root')!).render(
<Provider store={store}>
<QueryClientProvider client={qc}>
<MemoryRouter initialEntries={['/settings']}>
<div style={{ background: 'var(--bg)', color: 'var(--text)', padding: 24 }} data-capture-root>
<div style={{ maxWidth: 720 }}>
<AboutPanel />
</div>
</div>
</MemoryRouter>
</QueryClientProvider>
</Provider>,
)
7 changes: 7 additions & 0 deletions website/electron/auto-update.js
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,11 @@ function initAutoUpdate(deps) {
feedBase = process.env.KIROCREW_UPDATE_FEED || DEFAULT_FEED_BASE,
onUpdateState = null,
log = console,
// Whether a security policy provider currently governs this install's
// updates, making the Electron feed lane stand down. Wired by the
// caller (ipc-registrar.js) to a variable the renderer updates via
// update:notify-policy-governed once the gateway status is known.
isPolicyGoverned = () => false,
} = deps;

// Linux install shape. Resolved once, and BEFORE getInfo() is defined: the
Expand Down Expand Up @@ -1057,6 +1062,7 @@ function initAutoUpdate(deps) {
managedPath,
launchCheckDelayMs: LAUNCH_CHECK_DELAY_MS,
checkIntervalMs: CHECK_INTERVAL_MS,
isPolicyGoverned,
log,
});
}
Expand Down Expand Up @@ -1169,6 +1175,7 @@ function initAutoUpdate(deps) {
channelForVersion,
launchCheckDelayMs: LAUNCH_CHECK_DELAY_MS,
checkIntervalMs: CHECK_INTERVAL_MS,
isPolicyGoverned,
});
}

Expand Down
1 change: 1 addition & 0 deletions website/electron/gateway-supervisor.js
Original file line number Diff line number Diff line change
Expand Up @@ -1523,6 +1523,7 @@ function createGatewaySupervisor({
fetchRemoteToken,
entryUrl: dashboardEntryUrl,
probePrimaryPortOwner,
isCurrentBundleGateway,
stopForUpdate: stopGatewayForUpdate,
stopOnQuit: stopGatewayOnQuit,
// After sleep the old forward can look alive while carrying nothing, so a
Expand Down
Loading
Loading