Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e8e779e
fix(agentlet): compile harness definitions through platform shell
mydmdm Oct 8, 2026
7f436d7
fix(agenetes): recover from frozen harness intent
mydmdm Oct 8, 2026
c8c3210
fix(profiles): adopt harness discovery v2
mydmdm Oct 8, 2026
ceb08da
fix(server): use canonical public origin for reachback
mydmdm Oct 8, 2026
6ad163f
fix(server): stabilize public deployment endpoint
mydmdm Oct 8, 2026
8986d6c
Merge pull request #281 from microsoft/fix/issue-249
mydmdm Oct 8, 2026
b2cbf9f
Merge pull request #282 from microsoft/fix/issue-280
mydmdm Oct 8, 2026
7cd8762
feat(agentlet): add capability package SDK
mydmdm Oct 9, 2026
bb999ed
feat: add capability package provisioning
mydmdm Oct 9, 2026
38ffb9e
fix: add capability client license headers
mydmdm Oct 10, 2026
709d228
refactor: derive agent capability from manifest
mydmdm Oct 10, 2026
fcd10aa
Rename Agentlet capability SDK to service SDK
mydmdm Oct 10, 2026
1b31c8c
Rename capability packages to service packages
mydmdm Oct 10, 2026
5fbe2db
Make Agentlet Service SDK lease-only
mydmdm Oct 10, 2026
d04e289
Distribute executable Service Packages
mydmdm Oct 10, 2026
eb01c0e
Clarify Azure image Service entry
mydmdm Oct 10, 2026
c204453
Expose coherent Service runtime contexts
mydmdm Oct 10, 2026
8a618e5
Drive Service entry help from runtime context
mydmdm Oct 10, 2026
f8db321
fix: reconnect stalled canvas sync streams
mydmdm Oct 10, 2026
729db7a
Merge pull request #290 from microsoft/fix/issue-252
mydmdm Oct 10, 2026
191a79c
Merge pull request #286 from microsoft/fix/issue-110-v2
mydmdm Oct 10, 2026
ac0517b
feat(chat): add fragment comments to thread input
mydmdm Oct 11, 2026
ae491db
Merge pull request #294 from microsoft/fix/issue-293
mydmdm Oct 11, 2026
24eaf87
feat(chat): unify selected response promotion actions
mydmdm Oct 11, 2026
10d4879
Merge pull request #295 from microsoft/fix/issue-293
mydmdm Oct 12, 2026
07ab242
feat(agentlet): add explicit immutable agent bootstrap environment
mydmdm Oct 12, 2026
58f7dac
docs: document explicit Agentlet bootstrap and reachback contract
mydmdm Oct 12, 2026
afe5921
refactor(agentlet): remove host-specific Service SDK
mydmdm Oct 12, 2026
d3c8e3f
feat(agentlet-host): forward opaque host bootstrap to embedded daemon
mydmdm Oct 12, 2026
0ec97f8
feat: migrate Service SDK to Huabu client runtime and source daemon
mydmdm Oct 12, 2026
62a5d59
feat: add optional raw RFS client and private Service lease guide
mydmdm Oct 12, 2026
bada32c
feat: replace token settings with scoped source-daemon onboarding
mydmdm Oct 12, 2026
e4691a2
fix(client-runtime): add SDK test source header
mydmdm Oct 12, 2026
ccb4bad
feat(client-runtime): refine CLI Skills and Service commands
mydmdm Oct 12, 2026
4164e43
refactor(rfs): retire Space root guide override
mydmdm Oct 13, 2026
8caf05f
refactor(rfs): align Skill client presentations
mydmdm Oct 13, 2026
44120c5
fix(ci): build client runtime for storage profiles
mydmdm Oct 13, 2026
71aea68
refine rfs cli guidance and downloads
mydmdm Oct 13, 2026
154644d
Merge pull request #299 from microsoft/fix/issue-292
mydmdm Oct 14, 2026
efc92d5
fix(server): avoid bundle banner import alias conflicts
mydmdm Oct 14, 2026
4b83584
Merge branch 'main' into alpha
mydmdm Oct 14, 2026
66e935a
test: avoid dynamic CodeQL command inputs
mydmdm Oct 14, 2026
ca19a21
fix(web): reconcile first-turn question writes
mydmdm Oct 14, 2026
5ee1e0c
style: format connection token test
mydmdm Oct 14, 2026
97e53ae
Merge pull request #305 from microsoft/fix/issue-172
mydmdm Oct 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
36 changes: 22 additions & 14 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
# ══════════════════════════════════════════════════════════════════

# ── Ports ──
# Fixed Server port for `start:web`; startup fails if it is unavailable.
# Development launchers may advance from this preferred port for parallel use.
# SERVER_PORT=3001
# WEB_PORT=5173
# Preferred standalone handbook port; dev:desktop advances if it is occupied.
Expand All @@ -49,24 +51,30 @@
# HUABU_DEBUG_PROMPT=true

# ── External ACP agents (Copilot, Claude Code, …) ──
# Pairing itself is done in the Settings popover → "External Agents";
# codes are ephemeral and never written to disk (no env override).
# `ACP_URL` is only honoured by the `start-agentlet-daemon` wrapper when
# pointing the local CLI at a remote / TLS-fronted Huabu instance.
# ACP_URL=wss://my.host/api/acp/agent

# Settings > Agent > External Agent runtime > How to connect copies a
# source-daemon command or private connection env file. Run it from a
# compatible Huabu checkout after pnpm install; startup builds itself.
# Server credentials are fixed for the process lifetime: a nonempty value
# here, or a random per-boot fallback. Set this for stable remote connections.
# Old Settings token overrides are ignored but retained, not migrated/deleted.
# Generate a stable token: node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"
# HUABU_CONNECTION_TOKEN=
# ── Network exposure ──
# By default the server binds to 127.0.0.1 only, so a fresh install is
# never silently exposed to the LAN. Set HUABU_BIND_HOST=0.0.0.0 (or a
# specific NIC IP) to listen on every interface. When you do, you MUST
# also list every hostname/IP clients will use to reach the server in
# HUABU_ALLOWED_HOSTS — requests with any other Host header are
# rejected (defence against DNS rebinding). A non-loopback bind also
# REQUIRES both HUABU_BASIC_AUTH_* values; startup fails closed when
# either remote-access prerequisite is missing. Put HTTPS in front for
# anything beyond a trusted private network.
# specific NIC IP) to listen on every interface. A non-loopback bind requires
# HUABU_PUBLIC_ORIGIN and both HUABU_BASIC_AUTH_* values; startup fails closed
# when any prerequisite is missing. The public origin hostname is automatically
# trusted by the Host/CORS/Origin guards. Put HTTPS in front for anything beyond
# a trusted private network.
# HUABU_BIND_HOST=0.0.0.0
# HUABU_ALLOWED_HOSTS=192.168.1.50,huabu.team-a.example
# Canonical root origin reachable by remote Agentlets. Required for a
# non-loopback bind. Use HTTP only on a trusted private network; otherwise
# terminate HTTPS in front of Huabu. Paths, credentials, queries, and fragments
# are rejected. RFS and Agentlet endpoints are derived from this one value.
# HUABU_PUBLIC_ORIGIN=https://huabu.team-a.example
# Optional additional browser aliases beyond HUABU_PUBLIC_ORIGIN.
# HUABU_ALLOWED_HOSTS=192.168.1.50,huabu-alt.team-a.example

# ── Single-owner remote login ──
# Both values are required together. The authenticated owner can use every
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ jobs:
# The backend runs from source under tsx, so it needs the workspace
# packages built for the same reason the typecheck step does.
- name: Build workspace protocol/runtime packages
run: pnpm run build:agentlet && pnpm run build:agenetes
run: pnpm run build:agentlet && pnpm run build:agenetes && pnpm run build:client-runtime

# Mirrors the Testcontainers setup in
# `apps/server/src/test-support/storage-containers.ts`: the same pinned
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,14 @@ The remote address must be an origin without credentials, a query, fragment, or

```dotenv
HUABU_BIND_HOST=0.0.0.0
HUABU_ALLOWED_HOSTS=huabu.example.com
HUABU_PUBLIC_ORIGIN=https://huabu.example.com
HUABU_BASIC_AUTH_USER=owner
HUABU_BASIC_AUTH_PASS=<strong-password>
```

Then run `pnpm start:web`. Huabu rejects a non-loopback bind when allowed hosts or either Basic Auth value is missing. The authenticated owner can use all features, including Settings, OAuth, and External Agents. `pnpm dev` applies the same requirement to non-loopback browser clients while preserving zero-configuration local development.
Then run `pnpm start:web`. `HUABU_PUBLIC_ORIGIN` is the root HTTP(S) origin that remote Agentlets use to reach this deployment; Huabu derives both the Canvas RFS URL and Agentlet WebSocket endpoint from it and automatically trusts its hostname in the Host, CORS, and Origin guards. Set optional `HUABU_ALLOWED_HOSTS` only for additional browser aliases. Huabu rejects paths, credentials, queries, fragments, unsupported schemes, and a missing or loopback public origin for non-loopback binds. It also rejects a non-loopback bind when either Basic Auth value is missing. The authenticated owner can use all features, including Settings, OAuth, and External Agents. `pnpm dev` applies the same requirement to non-loopback browser clients while preserving zero-configuration local development.

`pnpm start:web` binds the fixed `SERVER_PORT` (default `3001`) and fails if that port is invalid or unavailable; it never advances to another port. Development launchers may advance from their preferred ports so intentional parallel instances remain possible.

Huabu currently serves HTTP. Use a trusted private network or terminate HTTPS with deployment infrastructure such as Caddy, Nginx, Tailscale Serve, or a cloud load balancer. Do not put a Basic Auth deployment on an untrusted network without transport encryption.

Expand Down
6 changes: 6 additions & 0 deletions apps/desktop/electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ extraResources:
# files bloat the installer for no benefit.
- '**/*.js'
- '**/*.js.map'
- 'package.json'
- from: ../server/dist-bundle/client-runtime
to: server/client-runtime
filter:
- '**/*.js'
- 'package.json'
- from: ../web/dist
to: web
filter:
Expand Down
5 changes: 4 additions & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
"type": "module",
"scripts": {
"dev": "tsx watch src/server.ts",
"predev": "pnpm --filter @huabu/client-runtime build",
"lint": "eslint .",
"format": "prettier --write .",
"build": "tsc -p tsconfig.json",
"prebundle": "node -e \"require('node:fs').rmSync('dist-bundle', { recursive: true, force: true })\"",
"prebuild": "pnpm --filter @huabu/client-runtime build",
"prebundle": "pnpm --filter @huabu/client-runtime build && node -e \"require('node:fs').rmSync('dist-bundle', { recursive: true, force: true })\"",
"bundle": "tsup",
"media:prepare": "node scripts/prepare-video-media.mjs",
"typecheck": "tsc -p tsconfig.json --noEmit",
Expand Down Expand Up @@ -40,6 +42,7 @@
"@fastify/rate-limit": "^11.2.0",
"@fastify/static": "^8.0.2",
"@huabu/shared": "workspace:*",
"@huabu/client-runtime": "workspace:*",
"@mozilla/readability": "^0.6.0",
"@opendocsg/pdf2md": "^0.2.5",
"@resvg/resvg-wasm": "^2.6.2",
Expand Down
26 changes: 17 additions & 9 deletions apps/server/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
getAgentProfileRegistry,
installAcpProfileCachePort,
mountAgenetes,
resolveDaemonEntry,
resolveDaemonRuntime,
} from './modules/agent/acp/index.js';
import { initializeAgentDefaults } from './modules/agent/agent-defaults.js';
import agentDefaultsRoutes from './modules/agent/agent-defaults.route.js';
Expand Down Expand Up @@ -55,6 +55,7 @@ import {
resolveAllowedHostnames,
} from './modules/security/index.js';
import { createApplicationRateLimitOptions } from './modules/security/rate-limit.js';
import serviceRoutes from './modules/services/services.route.js';
import { closeStorage } from './modules/storage/index.js';
import webRoutes from './modules/web/web.route.js';
import {
Expand Down Expand Up @@ -236,7 +237,8 @@ app.addHook('preHandler', async (request, reply) => {
!url.startsWith('/api/workspace') &&
!url.startsWith('/api/deployment') &&
!url.startsWith('/api/llm') &&
!url.startsWith('/api/integrations')
!url.startsWith('/api/integrations') &&
!url.startsWith('/api/services')
) {
return reply.status(503).send({
message:
Expand All @@ -257,6 +259,7 @@ app.register(artifactRoute, { prefix: '/api/canvas' });

app.register(llmRoutes, { prefix: '/api/llm' });
app.register(integrationsRoutes, { prefix: '/api/integrations' });
app.register(serviceRoutes, { prefix: '/api/services' });
app.register(deploymentRoutes, { prefix: '/api/deployment' });
app.register(canaryRedeployRoutes, {
prefix: '/api/deployment/canary',
Expand All @@ -272,8 +275,8 @@ app.register(rfsRoutes, { prefix: '/api/rfs' });
// It mounts the stateless Gateway, forks & supervises the agentlet daemon,
// and authenticates connections against the host-injected connection token.
// The daemon owns the agent worker pool; the server tells it which agent CLI
// to spawn (per user profile). The connection token never crosses the HTTP
// boundary — it lives only in-process and on the loopback WS.
// to spawn (per user profile). The token is revealed only by the explicit
// owner-only no-store copy action and is never part of ordinary read models.
//
// L1 owns all deployment-layout knowledge and injects it downward:
// the global connection token, the data directory, and the resolved
Expand All @@ -296,10 +299,17 @@ try {
app.log.warn({ err }, '[acp] could not remove legacy acp-config.json');
}
}
const daemonRuntime = resolveDaemonRuntime();
if (!daemonRuntime.artifacts.serviceSdkEntry) {
app.log.warn(
'[acp] Service SDK artifact missing; direct HTTP remains available, rebuild client-runtime to use JS Service entries',
);
}
const agentletGateway = mountAgenetes(app, {
connectionToken: getConnectionToken(),
dataDir: getDataDir(),
daemonEntryPath: resolveDaemonEntry() ?? '',
daemonEntryPath: daemonRuntime.artifacts.daemonEntry ?? '',
bootstrapEnv: daemonRuntime.bootstrapEnv,
getMaxAgents: () => getExternalAgentRuntimeConfig().maxAgents,
// Host-namespaced env isolation: the agentlet daemon and every external
// agent it spawns are host-agnostic and must receive their Huabu
Expand Down Expand Up @@ -341,10 +351,8 @@ app.addHook('onClose', async () => resetExternalNoteSessions());
// a connection-holding backend will need — a pool nobody closes leaks on
// every restart, and the lifecycle is where that is visible.
app.addHook('onClose', async () => closeStorage());
// Capture the bound TCP port for L1-owned reachback (RFS): the
// canvas-scoped `HUABU_RFS_URL` base is built from this. RFS is
// canvas-coupled and therefore a pure L1 concern, so the port lives in
// L1 rather than being read back out of the L2 transport host.
// Capture the bound TCP port for the local RFS reachback fallback. Public
// deployments use their configured canonical origin instead.
app.addHook('onListen', async () => {
const addr = app.server.address();
if (addr && typeof addr !== 'string') setHostServerPort(addr.port);
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/bind-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
* Operators who explicitly want LAN / remote access set
* `HUABU_BIND_HOST=0.0.0.0` (or a specific interface IP) and pair that
* with `HUABU_ALLOWED_HOSTS` and `HUABU_BASIC_AUTH_*` — see README.
* with `HUABU_PUBLIC_ORIGIN` and `HUABU_BASIC_AUTH_*` — see README.
*
* Lives in its own module (not inlined into `server.ts`) so the default
* can be regression-tested without booting Fastify. The Electron
Expand Down
Loading
Loading