Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b2f2f6f
flue-native: open evergreen integration branch
ZIJ Jul 5, 2026
821c68c
@opencomputer/flue: CF-native scaffolding (W4)
ZIJ Jul 5, 2026
0b5f580
feat(oc-gateway): productionize the Flue per-session gateway (W3)
ZIJ Jul 5, 2026
78b0f7b
feat(oc-gateway): W3 gateway to the resolved token seam (per-deploy t…
ZIJ Jul 5, 2026
d15bceb
Merge remote-tracking branch 'origin/main' into flue-native
ZIJ Jul 7, 2026
ef81aaa
Merge remote-tracking branch 'origin/flue-native' into w3/gateway-pro…
ZIJ Jul 7, 2026
e1ca4cb
Merge remote-tracking branch 'origin/flue-native' into flue-native-w4
ZIJ Jul 7, 2026
53a473c
fix(@opencomputer/flue): 3 W6-surfaced package defects
ZIJ Jul 7, 2026
eb87aaf
web: W11 sessions dashboard — runtime + spend on list, richer viewer,…
ZIJ Jul 7, 2026
f2406d3
Merge remote-tracking branch 'origin/flue-native-w4' into flue-native
ZIJ Jul 7, 2026
436cba2
feat(oc): oc agent deploy for the flue Worker-DO model (W7-c)
ZIJ Jul 7, 2026
224e9a7
feat(oc): stage flue build dir to R2, byte-free deploy (W7-c contract…
ZIJ Jul 8, 2026
023ba88
fix(oc-flue): bind gateway apiKey at run-scope route, not init (token…
ZIJ Jul 8, 2026
995ae44
fix(oc-flue): read OC_SESSION_TOKEN from request env (c.env) in route
ZIJ Jul 8, 2026
d30a3f7
fold: @opencomputer/flue token-seam fix (run-scope route c.env) into …
ZIJ Jul 8, 2026
89fdb0f
fold: oc agent deploy for the flue Worker-DO model (W7-c) into flue-n…
ZIJ Jul 8, 2026
2923857
fold: W11 sessions dashboard into flue-native (#489)
ZIJ Jul 8, 2026
0a1aa9c
fix(flue): scope gateway tokens and fence deploy epochs
ZIJ Jul 12, 2026
0946f4d
feat(flue): add config and secret CLI and SDK surfaces
ZIJ Jul 12, 2026
715721b
feat(web): manage Flue Worker config and write-only secrets
ZIJ Jul 12, 2026
8a84945
refactor(flue): narrow config to the preview path
ZIJ Jul 12, 2026
bf76b3d
Merge pull request #509 from diggerhq/fix/flue-product-complete
ZIJ Jul 12, 2026
c7c7694
Merge remote-tracking branch 'origin/main' into flue-native
ZIJ Jul 13, 2026
5c465a6
feat(agent): lock production gateway and Flue artifact boundary
ZIJ Jul 13, 2026
7a284e4
fix(agent-worker): close production boundary review gaps
ZIJ Jul 13, 2026
f0b769f
Merge pull request #513 from diggerhq/delta/agent-worker-production-p…
ZIJ Jul 13, 2026
195e041
fix(agent-worker): make runtime errors durably observable
ZIJ Jul 13, 2026
1769cc0
Merge pull request #515 from diggerhq/codex/agent-worker-error-telemetry
ZIJ Jul 13, 2026
67bf0ee
Merge remote-tracking branch 'origin/main' into fix/flue-dashboard-ro…
ZIJ Jul 13, 2026
10ec3f5
Merge pull request #516 from diggerhq/fix/flue-dashboard-rollout
ZIJ Jul 13, 2026
b0f913f
fix(cli): quiet non-actionable Flue deploy noise
ZIJ Jul 13, 2026
56904e1
fix(flue): resolve optional sandboxes on first use
ZIJ Jul 13, 2026
84c74b8
fix(web): render durable session turn state
ZIJ Jul 13, 2026
07e4677
Merge pull request #518 from diggerhq/delta/flue-fast-runtime-ui
ZIJ Jul 13, 2026
95c9903
Merge pull request #517 from diggerhq/fix/flue-deploy-noise
ZIJ Jul 13, 2026
6cb0d00
docs: explain the Flue runtime model
ZIJ Jul 14, 2026
409a1ca
Merge pull request #519 from diggerhq/docs/flue-runtime-guide
ZIJ Jul 14, 2026
ad76783
fix(flue): preserve valid worker build profiles
ZIJ Jul 14, 2026
5f7c372
release(flue): prepare the 0.2.0 npm package
ZIJ Jul 14, 2026
413cddb
fix(edge): redact request secrets and validate budgets
ZIJ Jul 14, 2026
f200dd8
fix(web): isolate live events and refresh sanitizer
ZIJ Jul 14, 2026
a6fd92d
Merge pull request #520 from diggerhq/fix/flue-release-readiness
ZIJ Jul 14, 2026
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
65 changes: 65 additions & 0 deletions .github/workflows/publish-flue-sdk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Publish Flue SDK

on:
push:
branches: [main]
paths:
- 'sdks/flue/**'
- '.github/workflows/publish-flue-sdk.yml'
workflow_dispatch:

concurrency:
group: publish-flue-sdk
cancel-in-progress: false

defaults:
run:
working-directory: sdks/flue

jobs:
publish:
name: Test, build & publish to npm
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '22.19'
registry-url: 'https://registry.npmjs.org'

- name: Install dependencies
run: npm ci

- name: Check if this exact version is already published
id: check
run: |
LOCAL_VERSION=$(node -p "require('./package.json').version")
if LOOKUP=$(npm view "@opencomputer/flue@$LOCAL_VERSION" version 2>&1); then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Version $LOCAL_VERSION is already published; skipping."
elif [[ "$LOOKUP" == *"E404"* ]]; then
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "Publishing version $LOCAL_VERSION."
else
echo "npm registry lookup failed; refusing to guess that the version is unpublished." >&2
echo "$LOOKUP" >&2
exit 1
fi

- name: Test
if: steps.check.outputs.skip == 'false'
run: npm test

- name: Build
if: steps.check.outputs.skip == 'false'
run: npm run build

- name: Publish
if: steps.check.outputs.skip == 'false'
run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
33 changes: 33 additions & 0 deletions PRODUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Product

## Register

product

## Users

Developers and operators who deploy OpenComputer agents, start and steer durable sessions, and diagnose their lifecycle from the dashboard. They need to understand current state, queued work, output, and failures without learning framework-specific internals.

## Product Purpose

OpenComputer provides a direct operational surface for creating, controlling, and observing agents and their sessions. Success means an operator can act quickly, trust that the UI reflects durable platform state, and identify a failure without changing or redeploying production code.

## Brand Personality

Calm, direct, and technical. The product should feel dependable and precise while staying approachable to a developer using it for the first time.

## Anti-references

Avoid decorative AI imagery, conversational gimmicks, theatrical loading states, ornamental motion, and framework-specific terminology in shared product concepts. Do not invent novel controls where a familiar operator affordance is clearer.

## Design Principles

1. Show durable truth. Status, ordering, errors, and completion must follow the platform record rather than optimistic animation.
2. Keep the operator in flow. Common actions and state changes should be immediate, compact, and easy to scan.
3. Explain failures where they occur. Use actionable language and preserve enough context to diagnose the problem.
4. Keep framework details behind neutral product concepts unless the framework itself is the subject.
5. Preserve the existing interface vocabulary. Consistency and earned familiarity matter more than novelty.

## Accessibility & Inclusion

Meet WCAG AA for contrast and interaction. Preserve keyboard and screen-reader semantics, never rely on color alone for state, and provide reduced-motion behavior for every animation.
72 changes: 72 additions & 0 deletions cloudflare-workers/log-tail/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import assert from "node:assert/strict";
import test from "node:test";
import worker from "./index.ts";

const env = {
AXIOM_HOST: "https://axiom.test",
AXIOM_DATASET: "edge",
AXIOM_TOKEN: "test-token",
};

test("records a silent HTTP 5xx even when the invocation outcome is ok", async () => {
const originalFetch = globalThis.fetch;
let records: Array<Record<string, unknown>> = [];
globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => {
records = JSON.parse(String(init?.body));
return new Response(null, { status: 200 });
}) as typeof fetch;

try {
await worker.tail([{
scriptName: "agt_test",
outcome: "ok",
eventTimestamp: Date.parse("2026-07-13T20:00:00Z"),
event: {
request: {
method: "POST",
url: "https://user:password@dispatch.test/agents/example/session?token=secret#fragment",
},
response: { status: 500 },
},
logs: [],
exceptions: [],
}], env, {} as ExecutionContext);
} finally {
globalThis.fetch = originalFetch;
}

assert.equal(records.length, 1);
assert.equal(records[0]?.level, "ERROR");
assert.equal(records[0]?.msg, "worker request returned HTTP 500");
assert.equal(records[0]?.service, "agt_test");
assert.equal(records[0]?.response_status, 500);
assert.equal(records[0]?.request_url, "https://dispatch.test/agents/example/session");
assert.equal(JSON.stringify(records[0]).includes("secret"), false);
assert.equal(JSON.stringify(records[0]).includes("password"), false);
});

test("fails the collector invocation when the durable sink rejects a batch", async () => {
const originalFetch = globalThis.fetch;
const originalConsoleError = console.error;
globalThis.fetch = (async () => new Response(
JSON.stringify({ code: 403, message: "not allowed to ingest into dataset" }),
{ status: 403 },
)) as typeof fetch;
console.error = () => {};

try {
await assert.rejects(
worker.tail([{
scriptName: "agt_test",
outcome: "exception",
eventTimestamp: Date.parse("2026-07-13T20:00:00Z"),
logs: [],
exceptions: [],
}], env, {} as ExecutionContext),
/axiom ingest failed status=403/,
);
} finally {
globalThis.fetch = originalFetch;
console.error = originalConsoleError;
}
});
40 changes: 33 additions & 7 deletions cloudflare-workers/log-tail/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ function safeStringify(v: unknown): string {
catch { return String(v); }
}

// Request query strings can carry browser client tokens (`?token=...`). Keep
// the route useful for diagnostics without copying credentials into Axiom.
function requestUrlForLogs(value?: string): string | undefined {
if (!value) return undefined;
try {
const url = new URL(value);
url.username = "";
url.password = "";
url.search = "";
url.hash = "";
return url.toString();
} catch {
return undefined;
}
}

// Map CF console level → Axiom-friendly level string matching what our Go
// slog handler emits ("INFO", "WARN", "ERROR", "DEBUG").
function level(l: TraceLog["level"]): string {
Expand All @@ -76,7 +92,7 @@ export default {
cell_id: "cf-edge",
region: "global",
outcome: item.outcome,
request_url: item.event?.request?.url,
request_url: requestUrlForLogs(item.event?.request?.url),
request_method: item.event?.request?.method,
response_status: item.event?.response?.status,
cron: item.event?.cron,
Expand Down Expand Up @@ -104,15 +120,20 @@ export default {
});
}

// Synthetic record when a request ended in exception/exceededCpu/etc
// but had no console output — gives us a row to count "failed
// requests by script" in Axiom without joining streams.
if (item.outcome !== "ok" && item.logs.length === 0 && item.exceptions.length === 0) {
// Synthetic record when a request failed without logging. A Worker can
// catch an exception and return 5xx while Cloudflare still reports the
// invocation outcome as "ok", so response status is part of the failure
// contract too. This keeps silent server errors searchable in Axiom.
const responseStatus = item.event?.response?.status;
const serverError = typeof responseStatus === "number" && responseStatus >= 500;
if ((item.outcome !== "ok" || serverError) && item.logs.length === 0 && item.exceptions.length === 0) {
records.push({
_time: new Date(item.eventTimestamp ?? Date.now()).toISOString(),
time: new Date(item.eventTimestamp ?? Date.now()).toISOString(),
level: "ERROR",
msg: `worker request ended with outcome=${item.outcome}`,
msg: serverError
? `worker request returned HTTP ${responseStatus}`
: `worker request ended with outcome=${item.outcome}`,
...baseEnvelope,
});
}
Expand All @@ -131,7 +152,12 @@ export default {
});
if (!resp.ok) {
const body = await resp.text().catch(() => "");
console.error(`axiom ingest failed status=${resp.status} body=${body.slice(0, 200)}`);
const message = `axiom ingest failed status=${resp.status} body=${body.slice(0, 200)}`;
console.error(message);
// The collector has Cloudflare Workers Logs enabled as an independent
// fallback. Throwing makes a broken durable sink an observable failed
// invocation instead of an apparently healthy, lossy success.
throw new Error(message);
}
},
};
14 changes: 12 additions & 2 deletions cloudflare-workers/log-tail/wrangler.prod.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# log-tail Worker — PROD on Mo's CF account.
# Tail consumer for opencomputer-edge-prod + opencomputer-events-ingest-prod.
# Forwards console output + exceptions to Axiom dataset cf-prod.
# Forwards console output + exceptions to Axiom dataset oc-platform-edge.
# Deploy: `wrangler deploy -c wrangler.prod.toml`

name = "opencomputer-log-tail-prod"
Expand All @@ -9,8 +9,18 @@ compatibility_date = "2024-12-01"
account_id = "b8f23cb87a7a6c64040d3134643da448"
workers_dev = false

# Independent fallback for failures in the Axiom sink itself. Tenant/dispatch
# logs go to Axiom; collector invocation failures remain searchable in the
# Cloudflare Observability dashboard even when Axiom is unavailable.
[observability]
enabled = true

[observability.logs]
invocation_logs = true
head_sampling_rate = 1

# Secrets (set via `wrangler secret put --config wrangler.prod.toml`):
# AXIOM_TOKEN — xaat-... ingest token for the cf-prod dataset
# AXIOM_TOKEN — ingest-capable token scoped to the oc-platform-edge dataset
[vars]
AXIOM_HOST = "https://api.axiom.co"
AXIOM_DATASET = "oc-platform-edge"
4 changes: 4 additions & 0 deletions cloudflare-workers/oc-gateway/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
.dev.vars
.wrangler/
dist/
Loading