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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ Use hidden `decodex serve --dev --listen-address 127.0.0.1:8912` only when
developing local account/app snapshot APIs against real runtime state while explicitly
avoiding scheduler activity. Dev mode deliberately does not register projects, poll
Linear, dispatch work, or accept `--config`. Decodex App's normal
fallback server is ordinary `decodex serve --listen-address 127.0.0.1:8912`; the CLI
fallback server is ordinary `decodex serve --listen-address 127.0.0.1:8192`; the CLI
owns the default scheduler cadences. App launch connects to an
existing live default listener instead of starting a duplicate server. For
dashboard-only UI work, prefer the mock server above.
Expand Down
2 changes: 1 addition & 1 deletion apps/decodex-app/.codex/environments/environment.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY
name = "Decodex App"
name = "Decodex"
version = 1

[setup]
Expand Down
6 changes: 3 additions & 3 deletions apps/decodex-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ bundled Rust app helper so account UI stays on the same CLI-owned files even whe
long-running local `decodex serve` is older than the app bundle. On launch the app also
connects to an existing `decodex serve` on the default local endpoint when one is
available; otherwise it starts the bundled Decodex binary as a normal scheduler with
`decodex serve --allow-unverified-codex --listen-address 127.0.0.1:8912`. The override
`decodex serve --allow-unverified-codex --listen-address 127.0.0.1:8192`. The override
only downgrades unverified Codex app-server identity to a warning; other preflight
blockers remain fail-closed. The CLI owns the default scheduler interval, currently
15 seconds. App-started servers load the enabled project registry and own the same
Expand Down Expand Up @@ -78,12 +78,12 @@ swift run --package-path apps/decodex-app DecodexApp
Use hidden `decodex serve --dev` only when manually testing local account APIs, the app
snapshot API, or dashboard routes while deliberately avoiding scheduler activity. The
normal app fallback is `decodex serve --allow-unverified-codex --listen-address
127.0.0.1:8912` so development Codex app-server builds can still run through the local
127.0.0.1:8192` so development Codex app-server builds can still run through the local
operator surface. Do not use `--dev` to validate project registration, Linear polling,
queue intake, or retained-lane execution; use ordinary `decodex serve` for those paths.

The staging script follows the local Rsnap-style signing path: it writes
`target/decodex-app/Decodex App.app`, signs the bundle with an Apple Development
`target/decodex-app/Decodex.app`, signs the bundle with an Apple Development
identity, enables hardened runtime, and verifies the signature before launch. Override
the signing identity with `DECODEX_APP_SIGN_IDENTITY`; override the staging directory
with `DECODEX_APP_STAGE_DIR`. Override the Rust profile with
Expand Down
30 changes: 22 additions & 8 deletions apps/decodex-app/Sources/DecodexApp/AccountPanelView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -678,11 +678,7 @@ struct AccountPanelView: View {
)
}
if let snapshot = displayableOperatorSnapshot {
rows.append(
snapshot.warningSummary == nil
? AccountPanelLayout.telemetryOperatorHeight
: AccountPanelLayout.telemetryOperatorHeightWithWarning
)
rows.append(operatorTelemetryHeight(for: snapshot))
}

guard rows.isEmpty == false else {
Expand All @@ -694,6 +690,19 @@ struct AccountPanelView: View {
+ CGFloat(rows.count - 1) * AccountPanelLayout.telemetryRowSpacing
}

private func operatorTelemetryHeight(for snapshot: OperatorSnapshotResponse) -> CGFloat {
var rows: [CGFloat] = [AccountPanelLayout.telemetryOperatorMetricHeight]
if snapshot.activeRuns.isEmpty == false {
rows.append(AccountRunChipLayout.height)
}
if snapshot.warningSummary != nil {
rows.append(AccountPanelLayout.telemetryOperatorWarningHeight)
}

return rows.reduce(0, +)
+ CGFloat(rows.count - 1) * AccountPanelLayout.telemetryOperatorRowSpacing
}

private func displayName(for account: CodexAccount) -> String {
if emailsHidden {
return AccountDisplay.aliases(for: store.accounts)[account.id]
Expand Down Expand Up @@ -1462,8 +1471,9 @@ private enum AccountPanelLayout {
static let telemetryProfileHeight: CGFloat = 50
static let telemetryPoolHeight: CGFloat = 16
static let telemetryPoolMeasuredHeight: CGFloat = 29
static let telemetryOperatorHeight: CGFloat = 16
static let telemetryOperatorHeightWithWarning: CGFloat = 36
static let telemetryOperatorMetricHeight: CGFloat = 16
static let telemetryOperatorWarningHeight: CGFloat = 16
static let telemetryOperatorRowSpacing: CGFloat = 4
static let noticeHeight: CGFloat = 44
static let minimumScrollableListHeight: CGFloat = 312

Expand Down Expand Up @@ -2501,7 +2511,7 @@ struct OperatorStatusStripView: View {
@Environment(\.colorScheme) private var colorScheme

var body: some View {
VStack(alignment: .leading, spacing: 4) {
VStack(alignment: .leading, spacing: AccountPanelLayout.telemetryOperatorRowSpacing) {
HStack(spacing: 5) {
ForEach(Array(metrics.enumerated()), id: \.element.id) { index, metric in
OperatorFlowMetricView(metric: metric)
Expand All @@ -2513,6 +2523,10 @@ struct OperatorStatusStripView: View {
}
.frame(height: 16)

if snapshot.activeRuns.isEmpty == false {
AccountRunSummaryView(runs: snapshot.activeRuns)
}

if let warning = snapshot.warningSummary {
HStack(alignment: .firstTextBaseline, spacing: 5) {
PanelMetricIconView(
Expand Down
4 changes: 2 additions & 2 deletions apps/decodex-app/Sources/DecodexApp/DecodexServerBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ actor DecodexServerBridge {
static let shared = DecodexServerBridge()

private let defaultBaseURL = DecodexServerBridge.makeDefaultBaseURL()
private let defaultListenAddress = "127.0.0.1:8912"
private let defaultListenAddress = "127.0.0.1:8192"
private let liveCheckFreshness: TimeInterval = 5
private var serverBaseURL: URL?
private var liveCheckBaseURL: URL?
Expand Down Expand Up @@ -282,7 +282,7 @@ actor DecodexServerBridge {
}

private static func makeDefaultBaseURL() -> URL {
guard let url = URL(string: "http://127.0.0.1:8912") else {
guard let url = URL(string: "http://127.0.0.1:8192") else {
preconditionFailure("default Decodex server URL must be valid")
}

Expand Down
26 changes: 26 additions & 0 deletions apps/decodex-app/Tests/DecodexAppTests/AccountModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,32 @@ final class AccountModelTests: XCTestCase {
XCTAssertTrue(snapshot.activeRuns(for: poolOnlyAccount).isEmpty)
}

func testOperatorSnapshotKeepsUnassignedActiveRunsVisibleGlobally() throws {
let account = makeAccount(
status: "available",
email: "pool@example.com",
accountFingerprint: "...654321"
)
let payload = """
{
"active_runs": [
{
"run_id": "run-unassigned",
"project_id": "pubfi-platform",
"issue_identifier": "PUB-1296",
"status": "running"
}
]
}
""".data(using: .utf8)!

let snapshot = try JSONDecoder().decode(OperatorSnapshotResponse.self, from: payload)

XCTAssertEqual(snapshot.activeRuns.map(\.runID), ["run-unassigned"])
XCTAssertEqual(snapshot.activeRunCount, 1)
XCTAssertTrue(snapshot.activeRuns(for: account).isEmpty)
}

func testOperatorSnapshotAssignsSelectedAccountWhenPrimaryAccountIsMissing() throws {
let assignedAccount = makeAccount(
status: "available",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import XCTest
final class DecodexServerBridgeTests: XCTestCase {
func testBundledServerArgumentsAllowUnverifiedCodex() {
XCTAssertEqual(
DecodexServerBridge.bundledServerArguments(listenAddress: "127.0.0.1:8912"),
DecodexServerBridge.bundledServerArguments(listenAddress: "127.0.0.1:8192"),
[
"serve",
"--allow-unverified-codex",
"--listen-address",
"127.0.0.1:8912",
"127.0.0.1:8192",
]
)
}
Expand Down
4 changes: 2 additions & 2 deletions apps/decodex-app/script/build_and_run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set -euo pipefail

MODE="${1:-run}"
PRODUCT_NAME="Decodex App"
PRODUCT_NAME="Decodex"
EXECUTABLE_NAME="DecodexApp"
HELPER_NAME="decodex-app-helper"
SERVER_NAME="decodex"
Expand Down Expand Up @@ -148,7 +148,7 @@ sign_staged_app_bundle() {
if ! resolve_signing_identity; then
echo "error: no valid macOS codesigning identity matching the configured signing selector was found." >&2
echo "error: import the real signing certificate or set DECODEX_APP_SIGN_IDENTITY to a valid identity." >&2
echo "error: Decodex App staging requires a stable codesigning identity." >&2
echo "error: Decodex.app staging requires a stable codesigning identity." >&2
exit 1
fi

Expand Down
13 changes: 7 additions & 6 deletions docs/reference/operator-control-plane.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ Decodex currently runs as a local, single-machine control plane:
Decodex App is a native shell over the same local runtime and account-pool state. On
launch it connects to an existing default local listener when one is reachable; if
not, it starts the bundled `decodex` binary as
`decodex serve --listen-address 127.0.0.1:8912`. The app fallback is a normal
control-plane server: it loads the registered project registry, schedules only enabled
projects, keeps active runtime DB-backed runs visible even when a project is disabled
`decodex serve --allow-unverified-codex --listen-address 127.0.0.1:8192`. The app
fallback is a normal control-plane server: it loads the registered project registry,
schedules only enabled projects, keeps active runtime DB-backed runs visible even when
a project is disabled
for future dispatch, uses the CLI-owned default cadences, and serves the dashboard,
account APIs, `GET /api/operator-snapshot`,
`POST /api/linear-scan`, `GET /api/lane/inspect`, and `POST /api/lane/interrupt` from
Expand All @@ -55,7 +56,7 @@ Agents that just created or relabeled queue issues can avoid waiting for the nex
5-minute Linear poll by sending a targeted local request:

```sh
curl -sS -X POST http://127.0.0.1:8912/api/linear-scan \
curl -sS -X POST http://127.0.0.1:8192/api/linear-scan \
-H 'Content-Type: application/json' \
-d '{"projectId":"decodex"}'
```
Expand All @@ -67,8 +68,8 @@ rate-limit backoff.
Lane inspect and interrupt are local control APIs, not dashboard UI actions:

```sh
curl -sS 'http://127.0.0.1:8912/api/lane/inspect?projectId=decodex&issue=XY-703'
curl -sS -X POST http://127.0.0.1:8912/api/lane/interrupt \
curl -sS 'http://127.0.0.1:8192/api/lane/inspect?projectId=decodex&issue=XY-703'
curl -sS -X POST http://127.0.0.1:8192/api/lane/interrupt \
-H 'Content-Type: application/json' \
-d '{"projectId":"decodex","issue":"XY-703","runId":"<run-id>"}'
```
Expand Down
8 changes: 4 additions & 4 deletions scripts/macos/test_decodex_app_stage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
set -euo pipefail

if [[ "$(uname -s)" != "Darwin" ]]; then
echo "Decodex App staging is macOS-only; skipping."
echo "Decodex.app staging is macOS-only; skipping."
exit 0
fi

./apps/decodex-app/script/build_and_run.sh stage

common_root="$(cd "$(git rev-parse --git-common-dir)/.." && pwd)"
stage_dir="${DECODEX_APP_STAGE_DIR:-$common_root/target/decodex-app}"
app_path="$stage_dir/Decodex App.app"
app_path="$stage_dir/Decodex.app"

test -d "$app_path"
test -x "$app_path/Contents/MacOS/DecodexApp"
Expand All @@ -27,8 +27,8 @@ codesign_details="$(codesign -dv --verbose=4 "$app_path" 2>&1)"
grep -q '^TeamIdentifier=' <<<"$codesign_details"
grep -q 'flags=.*runtime' <<<"$codesign_details"

plutil -extract CFBundleName raw "$app_path/Contents/Info.plist" | grep -qx 'Decodex App'
plutil -extract CFBundleDisplayName raw "$app_path/Contents/Info.plist" | grep -qx 'Decodex App'
plutil -extract CFBundleName raw "$app_path/Contents/Info.plist" | grep -qx 'Decodex'
plutil -extract CFBundleDisplayName raw "$app_path/Contents/Info.plist" | grep -qx 'Decodex'
plutil -extract CFBundleIconFile raw "$app_path/Contents/Info.plist" | grep -qx 'AppIcon'
plutil -extract CFBundleIdentifier raw "$app_path/Contents/Info.plist" | grep -qx 'space.decodex.app'
plutil -extract LSUIElement raw "$app_path/Contents/Info.plist" | grep -qx 'true'