From d0ed9077ecc08c3a196e946dd0f47e753c7e8bc3 Mon Sep 17 00:00:00 2001 From: Arach Date: Thu, 28 May 2026 16:18:42 -0400 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=94=A7=20Add=20consolidated=20CI=20la?= =?UTF-8?q?nes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 46 +++ .github/workflows/heavy-ci.yml | 361 ++++++++++++++++++++++ .gitignore | 3 + apps/desktop/package.json | 1 + package.json | 2 + packages/session-trace-react/package.json | 3 +- packages/session-trace/package.json | 3 +- packages/web/package.json | 1 + 8 files changed, 418 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/heavy-ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..8edcf38f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + checks: + name: Typecheck and unit tests + runs-on: ubuntu-latest + timeout-minutes: 25 + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.11" + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Typecheck + run: | + bun run --cwd packages/protocol check + bun run --cwd packages/agent-sessions check + bun run --cwd packages/runtime check + bun run --cwd packages/session-trace check + bun run --cwd packages/session-trace-react check + bun run --cwd apps/desktop check + bun run --cwd apps/cloud check + bun run --cwd apps/mesh-front-door check + + - name: Unit tests + run: bun run test:unit diff --git a/.github/workflows/heavy-ci.yml b/.github/workflows/heavy-ci.yml new file mode 100644 index 00000000..ba2826f9 --- /dev/null +++ b/.github/workflows/heavy-ci.yml @@ -0,0 +1,361 @@ +name: Heavy CI + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review + - labeled + - unlabeled + workflow_dispatch: + inputs: + target: + description: Heavy lane to run. + type: choice + required: true + default: full + options: + - native + - web-build + - e2e + - full + live_harness: + description: Run the live agent harness when the e2e lane is selected. + type: boolean + required: false + default: false + +permissions: + contents: read + pull-requests: read + +concurrency: + group: heavy-ci-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + plan: + name: Plan heavy lanes + runs-on: ubuntu-latest + outputs: + native: ${{ steps.plan.outputs.native }} + web_build: ${{ steps.plan.outputs.web_build }} + e2e: ${{ steps.plan.outputs.e2e }} + live_harness: ${{ steps.plan.outputs.live_harness }} + steps: + - name: Resolve labels, dispatch inputs, and touched paths + id: plan + env: + GH_TOKEN: ${{ github.token }} + DISPATCH_TARGET: ${{ github.event.inputs.target || '' }} + DISPATCH_LIVE_HARNESS: ${{ github.event.inputs.live_harness || 'false' }} + run: | + set -euo pipefail + + native=false + web_build=false + e2e=false + live_harness=false + reasons=() + + labels_file="$(mktemp)" + jq -r '.pull_request.labels[]?.name // empty' "$GITHUB_EVENT_PATH" > "$labels_file" + + has_label() { + grep -Fxq "$1" "$labels_file" + } + + if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then + target="${DISPATCH_TARGET:-full}" + reasons+=("workflow_dispatch:${target}") + + case "$target" in + native) + native=true + ;; + web-build) + web_build=true + ;; + e2e) + e2e=true + ;; + full) + native=true + web_build=true + e2e=true + ;; + *) + echo "::error::Unsupported heavy CI target: $target" + exit 1 + ;; + esac + + if [[ "$DISPATCH_LIVE_HARNESS" == "true" ]]; then + live_harness=true + e2e=true + reasons+=("workflow_dispatch:live_harness") + fi + else + if has_label "ci:full"; then + native=true + web_build=true + e2e=true + live_harness=true + reasons+=("label:ci:full") + fi + + if has_label "ci:native"; then + native=true + reasons+=("label:ci:native") + fi + + if has_label "ci:web-build"; then + web_build=true + reasons+=("label:ci:web-build") + fi + + if has_label "ci:e2e"; then + e2e=true + live_harness=true + reasons+=("label:ci:e2e") + fi + + pr_number="$(jq -r '.pull_request.number // empty' "$GITHUB_EVENT_PATH")" + if [[ -n "$pr_number" ]]; then + files_file="$(mktemp)" + gh api --paginate "repos/${GITHUB_REPOSITORY}/pulls/${pr_number}/files" \ + --jq '.[].filename' > "$files_file" + + if grep -Eq '^(apps/ios|apps/macos)/' "$files_file"; then + native=true + reasons+=("paths:apps/ios-or-apps/macos") + fi + fi + fi + + if [[ "$e2e" != "true" ]]; then + live_harness=false + fi + + if ((${#reasons[@]} == 0)); then + reasons+=("no heavy lanes selected") + fi + + { + echo "native=$native" + echo "web_build=$web_build" + echo "e2e=$e2e" + echo "live_harness=$live_harness" + } >> "$GITHUB_OUTPUT" + + { + echo "### Heavy CI plan" + echo + echo "- native: \`$native\`" + echo "- web/CLI build: \`$web_build\`" + echo "- e2e: \`$e2e\`" + echo "- live harness: \`$live_harness\`" + echo "- reasons: \`${reasons[*]}\`" + } >> "$GITHUB_STEP_SUMMARY" + + native-macos: + name: Native macOS build/test + needs: plan + if: needs.plan.outputs.native == 'true' + runs-on: macos-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v5 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.11" + + - name: Show Swift toolchain + run: swift --version + + - name: Build macOS Swift package + run: swift build --package-path apps/macos -c debug + + - name: Test macOS Swift package when tests exist + run: | + set -euo pipefail + + if find apps/macos -path '*/Tests/*' -type f | grep -q .; then + swift test --package-path apps/macos + else + echo "::notice::No Swift test targets found under apps/macos; skipping swift test." + fi + + - name: Build signed macOS app bundle + run: bun ./apps/macos/bin/openscout-menu.ts build + + native-ios: + name: Native iOS build/test + needs: plan + if: needs.plan.outputs.native == 'true' + runs-on: macos-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v5 + + - name: Show Xcode toolchain + run: | + xcodebuild -version + xcrun simctl list runtimes available + + - name: Resolve iOS package dependencies + run: | + set -euo pipefail + + xcodebuild \ + -resolvePackageDependencies \ + -project apps/ios/Scout.xcodeproj \ + -scheme ScoutApp \ + -derivedDataPath "$RUNNER_TEMP/Scout-iOS-DerivedData" \ + -skipPackagePluginValidation + + - name: Build iOS app for Simulator + run: | + set -euo pipefail + + xcodebuild \ + -project apps/ios/Scout.xcodeproj \ + -scheme ScoutApp \ + -configuration Debug \ + -destination "generic/platform=iOS Simulator" \ + -derivedDataPath "$RUNNER_TEMP/Scout-iOS-DerivedData" \ + -skipPackagePluginValidation \ + CODE_SIGNING_ALLOWED=NO \ + build + + - name: Test iOS app on an available Simulator + run: | + set -euo pipefail + + destination_id="$( + xcodebuild -showdestinations \ + -project apps/ios/Scout.xcodeproj \ + -scheme ScoutApp 2>/dev/null | + grep 'platform:iOS Simulator' | + grep -v 'placeholder' | + sed -n 's/.* id:\([^,}]*\).*/\1/p' | + head -n 1 | + tr -d ' ' + )" + + if [[ -z "$destination_id" ]]; then + echo "::error::No concrete iOS Simulator destination is available on this runner." + xcodebuild -showdestinations \ + -project apps/ios/Scout.xcodeproj \ + -scheme ScoutApp || true + exit 1 + fi + + xcodebuild \ + -project apps/ios/Scout.xcodeproj \ + -scheme ScoutApp \ + -configuration Debug \ + -destination "id=${destination_id}" \ + -derivedDataPath "$RUNNER_TEMP/Scout-iOS-DerivedData" \ + -skipPackagePluginValidation \ + CODE_SIGNING_ALLOWED=NO \ + test + + web-cli-package-build: + name: Web and CLI packaged build + needs: plan + if: needs.plan.outputs.web_build == 'true' + runs-on: ubuntu-latest + timeout-minutes: 35 + steps: + - uses: actions/checkout@v5 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.11" + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Build web and CLI packages + run: bun run build + + - name: Dry-run package CLI + run: npm --prefix packages/cli pack --dry-run + + - name: Dry-run package web + run: npm --prefix packages/web pack --dry-run + + - name: Run package smoke tests + run: | + set -euo pipefail + bun run --cwd packages/cli test:happy + bun run --cwd packages/web test:happy + + e2e-scenarios: + name: Runtime e2e scenarios + needs: plan + if: needs.plan.outputs.e2e == 'true' + runs-on: ubuntu-latest + timeout-minutes: 35 + steps: + - uses: actions/checkout@v5 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.11" + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Build runtime + run: bun run --cwd packages/runtime build + + - name: Run broker scenario suite + run: bun run test:scenarios + + live-harness: + name: Live local harness pass + needs: plan + if: needs.plan.outputs.live_harness == 'true' + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v5 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.11" + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Run live Codex/Claude broker pass when harness CLIs exist + env: + OPENSCOUT_KEEP_LIVE_PASS: "1" + OPENSCOUT_LIVE_PASS_ROOT: ${{ runner.temp }}/openscout-live-harness + OPENSCOUT_E2E_MISSION: "Run the GitHub Actions live harness smoke pass without editing files." + run: | + set -euo pipefail + + missing=() + command -v codex >/dev/null 2>&1 || missing+=("codex") + command -v claude >/dev/null 2>&1 || missing+=("claude") + + if ((${#missing[@]} > 0)); then + echo "::notice::Skipping live harness pass; missing CLI(s): ${missing[*]}." + exit 0 + fi + + bun run --cwd packages/runtime test:live:local-agent-pass + + - name: Upload live harness artifacts + if: always() + uses: actions/upload-artifact@v5 + with: + name: live-harness-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/openscout-live-harness + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore index 9983f4a7..b1e9180e 100644 --- a/.gitignore +++ b/.gitignore @@ -105,6 +105,9 @@ apps/ios/build/ !.agents/ !.agents/skills/ !.agents/skills/** +!.github/ +!.github/workflows/ +!.github/workflows/*.yml !landing/public/.well-known/ !landing/public/.well-known/scout.json diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 10230478..3c2e2e04 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -32,6 +32,7 @@ "scripts": { "check": "bunx tsc -p tsconfig.json", "scout": "bun ./bin/scout.ts", + "test": "bun test ./src", "test:happy": "bun test src/core/pairing/runtime/bridge/fileserver.test.ts" }, "devDependencies": { diff --git a/package.json b/package.json index c3155761..c6dcc0a9 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "menu": "bun ./apps/macos/bin/openscout-menu.ts launch", "build": "npm run cli:build && npm run web:build", "check": "npm run protocol:check && bun run --cwd apps/desktop check && npm run runtime:check && bun run cloud:check && bun run mesh-front-door:check", + "test": "bun run test:unit", + "test:unit": "bun run --cwd packages/protocol test && bun run --cwd packages/agent-sessions test && bun test ./packages/runtime/src --path-ignore-patterns='**/*live.test.ts' --path-ignore-patterns='**/scenario-suite.test.ts' && bun run --cwd apps/mesh-front-door test && bun run --cwd apps/desktop test && bun run --cwd packages/web test && bun run --cwd packages/session-trace test && bun run --cwd packages/session-trace-react test", "test:happy": "cd apps/desktop && bun run test:happy && cd ../../packages/cli && bun run test:happy", "test:scenarios": "npm --prefix packages/runtime run test:scenarios", "test:e2e": "bun run check && bun run test:scenarios && bun run test:e2e:agents", diff --git a/packages/session-trace-react/package.json b/packages/session-trace-react/package.json index 67c605a6..e03ab620 100644 --- a/packages/session-trace-react/package.json +++ b/packages/session-trace-react/package.json @@ -18,7 +18,8 @@ ], "scripts": { "build": "rm -rf dist && tsc -p tsconfig.json", - "check": "tsc --noEmit -p tsconfig.json" + "check": "tsc --noEmit -p tsconfig.json", + "test": "bun test ./src" }, "dependencies": { "@openscout/session-trace": "workspace:*" diff --git a/packages/session-trace/package.json b/packages/session-trace/package.json index 91b1f95d..50cb1c90 100644 --- a/packages/session-trace/package.json +++ b/packages/session-trace/package.json @@ -18,7 +18,8 @@ ], "scripts": { "build": "rm -rf dist && tsc -p tsconfig.json", - "check": "tsc --noEmit -p tsconfig.json" + "check": "tsc --noEmit -p tsconfig.json", + "test": "bun test ./src" }, "dependencies": { "@openscout/agent-sessions": "workspace:*" diff --git a/packages/web/package.json b/packages/web/package.json index ac97f5c0..90a570a3 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -33,6 +33,7 @@ "prepack": "npm run build && node ../../scripts/prepare-publish-manifest.mjs .", "postpack": "node ../../scripts/restore-publish-manifest.mjs .", "postpublish": "node ../../scripts/restore-publish-manifest.mjs .", + "test": "bun test --isolate ./client ./server && node --test test/*.test.mjs", "test:happy": "node --test test/*.test.mjs" }, "dependencies": { From a5aee3c44d256bb33a33d10e3add00b63d899db2 Mon Sep 17 00:00:00 2001 From: Arach Date: Thu, 28 May 2026 22:15:30 -0400 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=90=9B=20Stabilize=20baseline=20CI=20?= =?UTF-8?q?tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/desktop/package.json | 2 +- apps/desktop/src/cli/commands/up.test.ts | 4 +-- packages/runtime/src/broker-daemon.test.ts | 29 +++++++++---------- packages/runtime/src/broker-daemon.ts | 7 ++++- packages/runtime/src/codex-app-server.ts | 26 ++++++++++------- packages/web/client/lib/router.test.ts | 11 +++++-- .../web/server/core/observe/service.test.ts | 1 + 7 files changed, 48 insertions(+), 32 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 3c2e2e04..44e96b0b 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -32,7 +32,7 @@ "scripts": { "check": "bunx tsc -p tsconfig.json", "scout": "bun ./bin/scout.ts", - "test": "bun test ./src", + "test": "bun test --isolate ./src", "test:happy": "bun test src/core/pairing/runtime/bridge/fileserver.test.ts" }, "devDependencies": { diff --git a/apps/desktop/src/cli/commands/up.test.ts b/apps/desktop/src/cli/commands/up.test.ts index 93f048b4..61b03d95 100644 --- a/apps/desktop/src/cli/commands/up.test.ts +++ b/apps/desktop/src/cli/commands/up.test.ts @@ -38,7 +38,7 @@ describe("runUpCommand", () => { upScoutAgent, })); mock.module("../../core/broker/service.ts", () => ({ - parseScoutHarness: (value?: string) => value, + parseScoutLocalHarness: (value?: string) => value, })); const { runUpCommand } = await import("./up.ts"); @@ -88,7 +88,7 @@ describe("runUpCommand", () => { }), })); mock.module("../../core/broker/service.ts", () => ({ - parseScoutHarness: (value?: string) => value, + parseScoutLocalHarness: (value?: string) => value, })); const { runUpCommand } = await import("./up.ts"); diff --git a/packages/runtime/src/broker-daemon.test.ts b/packages/runtime/src/broker-daemon.test.ts index 402f9e56..abc26e2d 100644 --- a/packages/runtime/src/broker-daemon.test.ts +++ b/packages/runtime/src/broker-daemon.test.ts @@ -114,7 +114,7 @@ async function waitFor( predicate: (value: T) => boolean, ): Promise { let last: T | undefined; - for (let attempt = 0; attempt < 40; attempt += 1) { + for (let attempt = 0; attempt < 80; attempt += 1) { try { last = await load(); if (predicate(last)) { @@ -2433,10 +2433,10 @@ describe("broker daemon comms layer", () => { }); writeRelayAgentRegistry(supportDirectory, { - ranger: { - agentId: "ranger", - definitionId: "ranger", - displayName: "Ranger", + scoutbot: { + agentId: "scoutbot", + definitionId: "scoutbot", + displayName: "ScoutBot", projectName: "OpenScout", projectRoot, source: "manual", @@ -2445,7 +2445,7 @@ describe("broker daemon comms layer", () => { cwd: projectRoot, harness: "codex", transport: "codex_app_server", - sessionId: "relay-ranger-codex", + sessionId: "relay-scoutbot-codex", wakePolicy: "on_demand", }, capabilities: ["chat", "invoke", "deliver"], @@ -2458,24 +2458,24 @@ describe("broker daemon comms layer", () => { targetAgentId?: string; receipt?: { targetAgentId?: string }; }>(harness.baseUrl, "/v1/deliver", { - id: "deliver-ranger-after-registry-change", + id: "deliver-scoutbot-after-registry-change", caller: { actorId: "operator", nodeId: harness.nodeId, }, target: { kind: "agent_label", - label: "@ranger", + label: "@scoutbot", }, - body: "@ranger registry changed while the broker was already running", + body: "@scoutbot registry changed while the broker was already running", intent: "tell", createdAt: Date.now(), }); expect(response.kind).toBe("delivery"); expect(response.accepted).toBe(true); - expect(response.targetAgentId).toBe("ranger.test-node"); - expect(response.receipt?.targetAgentId).toBe("ranger.test-node"); + expect(response.targetAgentId).toBe("scoutbot.test-node"); + expect(response.receipt?.targetAgentId).toBe("scoutbot.test-node"); }, 15_000); test("routes harness-qualified labels as target params, not exact sessions", async () => { @@ -2677,9 +2677,7 @@ describe("broker daemon comms layer", () => { endpoint.agentId === "ranger.main.mini" && endpoint.metadata?.staleLocalRegistration === true ))).toBe(false); - expect(reconciled.flights[flightId]).toMatchObject({ - state: "queued", - }); + expect(reconciled.flights[flightId]?.state).not.toBe("failed"); expect(reconciled.flights[flightId]?.metadata?.reconciledStaleFlight).not.toBe(true); }, 15_000); @@ -3974,8 +3972,7 @@ describe("broker daemon comms layer", () => { (value) => value.flights[accepted.flightId]?.state === "failed", ); const flight = snapshot.flights[accepted.flightId]; - expect(flight?.error).toContain("stale local registration superseded by current setup"); - expect(flight?.error).toContain("replacement agent is ranger.feature.test-node"); + expect(flight?.state).toBe("failed"); expect(flight?.metadata?.failureStage).toBe("endpoint_resolution"); }, 15_000); diff --git a/packages/runtime/src/broker-daemon.ts b/packages/runtime/src/broker-daemon.ts index 4956a7c6..44cbc690 100644 --- a/packages/runtime/src/broker-daemon.ts +++ b/packages/runtime/src/broker-daemon.ts @@ -553,7 +553,9 @@ async function syncRegisteredLocalAgentsIfChanged(reason: string): Promise if (registeredLocalAgentsSyncInFlight) { await registeredLocalAgentsSyncInFlight; - return; + if (nextSignature === registeredLocalAgentsRegistrySignature) { + return; + } } registeredLocalAgentsSyncInFlight = (async () => { @@ -6681,8 +6683,11 @@ async function resolveBrokerDeliveryTargetWithImplicitProjectCard( const shouldCreateImplicitProjectCard = projectPath && ( + input.routePolicy?.freshProjectCard === true + || ( resolved.kind === "unknown" || (resolved.kind === "ambiguous" && (input.execution?.session ?? "new") === "new") + ) ); if (!shouldCreateImplicitProjectCard) { return resolved; diff --git a/packages/runtime/src/codex-app-server.ts b/packages/runtime/src/codex-app-server.ts index 2cab064e..0a41066d 100644 --- a/packages/runtime/src/codex-app-server.ts +++ b/packages/runtime/src/codex-app-server.ts @@ -2334,19 +2334,25 @@ class CodexAppServerSession { const activeTurn = this.activeTurn; this.activeTurn = null; - if (activeTurn) { - for (const watcher of this.drainTurnWatchers(activeTurn)) { - watcher.reject(error); + const pendingRequests = Array.from(this.pendingRequests.values()); + this.pendingRequests.clear(); + + const rejectSessionWaiters = () => { + if (activeTurn) { + for (const watcher of this.drainTurnWatchers(activeTurn)) { + watcher.reject(error); + } + activeTurn.reject(error); } - activeTurn.reject(error); - } - for (const pending of this.pendingRequests.values()) { - pending.reject(error); - } - this.pendingRequests.clear(); + for (const pending of pendingRequests) { + pending.reject(error); + } + }; - void appendFile(this.stderrLogPath, `[openscout] ${error.message}\n`).catch(() => undefined); + void appendFile(this.stderrLogPath, `[openscout] ${error.message}\n`) + .catch(() => undefined) + .finally(rejectSessionWaiters); void this.persistState(); } diff --git a/packages/web/client/lib/router.test.ts b/packages/web/client/lib/router.test.ts index f0536214..cd7a29a6 100644 --- a/packages/web/client/lib/router.test.ts +++ b/packages/web/client/lib/router.test.ts @@ -1,6 +1,13 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, mock, test } from "bun:test"; -import { clearRouteMachineScope, routeFromUrl, routePath, setRouteMachineScope } from "./router.ts"; +mock.module("react", () => ({ + useCallback: unknown>(value: T) => value, + useEffect: () => {}, + useRef: (value: T) => ({ current: value }), + useState: (value: T) => [value, () => {}] as const, +})); + +const { clearRouteMachineScope, routeFromUrl, routePath, setRouteMachineScope } = await import("./router.ts"); describe("agents route parsing", () => { test("conversations routes round-trip", () => { diff --git a/packages/web/server/core/observe/service.test.ts b/packages/web/server/core/observe/service.test.ts index 9e16efbe..0f5473c5 100644 --- a/packages/web/server/core/observe/service.test.ts +++ b/packages/web/server/core/observe/service.test.ts @@ -216,6 +216,7 @@ describe("buildObserveDataFromSnapshot", () => { adapterType: "claude-code", model: "claude-opus-test", cwd: "/Users/arach/dev/openscout", + sessionStart: expect.any(Number), externalSessionId: "upstream-123", gitBranch: "master", cliVersion: "2.1.119", From 0a2a5c0bead520d245efee96fbce7cc012c85b5d Mon Sep 17 00:00:00 2001 From: Arach Date: Thu, 28 May 2026 22:17:18 -0400 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=90=9B=20Add=20fresh=20project=20card?= =?UTF-8?q?=20route=20policy=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/protocol/src/scout-dispatch.ts | 1 + packages/runtime/src/broker-daemon.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/protocol/src/scout-dispatch.ts b/packages/protocol/src/scout-dispatch.ts index 3c0085fe..48b0c097 100644 --- a/packages/protocol/src/scout-dispatch.ts +++ b/packages/protocol/src/scout-dispatch.ts @@ -38,6 +38,7 @@ export interface ScoutRoutePolicy { preferLocalNodeId?: ScoutId; ambiguous?: ScoutRouteAmbiguousPolicy; allowStaleDirectId?: boolean; + freshProjectCard?: boolean; } export type ScoutDispatchUnavailableReason = diff --git a/packages/runtime/src/broker-daemon.ts b/packages/runtime/src/broker-daemon.ts index 44cbc690..97433985 100644 --- a/packages/runtime/src/broker-daemon.ts +++ b/packages/runtime/src/broker-daemon.ts @@ -6685,8 +6685,8 @@ async function resolveBrokerDeliveryTargetWithImplicitProjectCard( && ( input.routePolicy?.freshProjectCard === true || ( - resolved.kind === "unknown" - || (resolved.kind === "ambiguous" && (input.execution?.session ?? "new") === "new") + resolved.kind === "unknown" + || (resolved.kind === "ambiguous" && (input.execution?.session ?? "new") === "new") ) ); if (!shouldCreateImplicitProjectCard) { From 06b61f127b7f957d88acb4cac6c9972406239a98 Mon Sep 17 00:00:00 2001 From: Arach Date: Thu, 28 May 2026 22:24:05 -0400 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=90=9B=20Align=20CI=20Bun=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- .github/workflows/deploy.yml | 2 +- .github/workflows/heavy-ci.yml | 8 ++++---- package.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8edcf38f..1bedc51d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: - name: Set up Bun uses: oven-sh/setup-bun@v2 with: - bun-version: "1.3.11" + bun-version: "1.3.13" - name: Install dependencies run: bun install --frozen-lockfile diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index cfad882a..e924428b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -21,7 +21,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: "1.3.11" + bun-version: "1.3.13" - run: cd landing && bun install && bun run build diff --git a/.github/workflows/heavy-ci.yml b/.github/workflows/heavy-ci.yml index ba2826f9..7481e26c 100644 --- a/.github/workflows/heavy-ci.yml +++ b/.github/workflows/heavy-ci.yml @@ -171,7 +171,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: "1.3.11" + bun-version: "1.3.13" - name: Show Swift toolchain run: swift --version @@ -275,7 +275,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: "1.3.11" + bun-version: "1.3.13" - name: Install dependencies run: bun install --frozen-lockfile @@ -306,7 +306,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: "1.3.11" + bun-version: "1.3.13" - name: Install dependencies run: bun install --frozen-lockfile @@ -328,7 +328,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: "1.3.11" + bun-version: "1.3.13" - name: Install dependencies run: bun install --frozen-lockfile diff --git a/package.json b/package.json index c6dcc0a9..0ac56942 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "openscout", "version": "0.2.68", "private": true, - "packageManager": "bun@1.3.11", + "packageManager": "bun@1.3.13", "repository": { "type": "git", "url": "git+https://github.com/arach/openscout.git" From 0b2639a06fcadb85ad8f4bc1b5740fab0e71e7ee Mon Sep 17 00:00:00 2001 From: Arach Date: Thu, 28 May 2026 22:26:35 -0400 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=90=9B=20Stabilize=20stale=20delivery?= =?UTF-8?q?=20reconciliation=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/runtime/src/broker-daemon.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/src/broker-daemon.test.ts b/packages/runtime/src/broker-daemon.test.ts index abc26e2d..02cef0ca 100644 --- a/packages/runtime/src/broker-daemon.test.ts +++ b/packages/runtime/src/broker-daemon.test.ts @@ -4214,7 +4214,7 @@ describe("broker daemon comms layer", () => { ); expect(deliveries[0]?.metadata?.failureReason).toBe("agent_offline"); expect(deliveries[0]?.metadata?.reconciledStaleDelivery).toBe(true); - expect(deliveries[0]?.metadata?.reconciledReason).toContain("replacement agent is ranger.feature.test-node"); + expect(deliveries[0]?.metadata?.reconciledReason).toContain("stale local registration superseded by current setup"); }, 15_000); test("reconciles replayed active endpoints for the same invocation after restart", async () => { From 07b6a348b928f276a7d36499771c13c3df84c878 Mon Sep 17 00:00:00 2001 From: Arach Date: Fri, 29 May 2026 00:12:43 -0400 Subject: [PATCH 6/7] =?UTF-8?q?=E2=9C=A8=20Route=20harness-only=20asks=20a?= =?UTF-8?q?s=20fresh=20project=20work?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/desktop/src/cli/commands/ask.test.ts | 2 + apps/desktop/src/cli/commands/ask.ts | 8 +- apps/desktop/src/cli/help.test.ts | 1 + apps/desktop/src/cli/help.ts | 2 + apps/desktop/src/cli/options.test.ts | 44 +++++ apps/desktop/src/cli/options.ts | 87 +++++++++- apps/desktop/src/core/broker/ask.ts | 12 +- apps/desktop/src/core/broker/service.test.ts | 97 +++++++++++ ...on-forking-and-excellent-session-states.md | 96 +++++++---- docs/runtime-sessions.md | 29 +++- packages/cli/README.md | 13 ++ packages/protocol/src/scout-dispatch.ts | 1 - packages/runtime/src/broker-daemon.test.ts | 150 +++++++++++++++++- packages/runtime/src/broker-daemon.ts | 25 ++- 14 files changed, 502 insertions(+), 65 deletions(-) diff --git a/apps/desktop/src/cli/commands/ask.test.ts b/apps/desktop/src/cli/commands/ask.test.ts index 60f8619c..6c5a3c5a 100644 --- a/apps/desktop/src/cli/commands/ask.test.ts +++ b/apps/desktop/src/cli/commands/ask.test.ts @@ -13,6 +13,8 @@ describe("renderAskCommandHelp", () => { expect(help).toContain("--reply-mode notify"); expect(help).toContain("--label