@hybridaione/hybridclaw 0.25.2 → 0.25.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/AGENTS.md +7 -0
  2. package/CHANGELOG.md +44 -0
  3. package/README.md +11 -3
  4. package/console/package.json +1 -1
  5. package/container/dist/mcp/config-watcher.js +22 -2
  6. package/container/dist/mcp/config-watcher.js.map +1 -1
  7. package/container/npm-shrinkwrap.json +2 -2
  8. package/container/package-lock.json +2 -2
  9. package/container/package.json +1 -1
  10. package/container/src/mcp/config-watcher.ts +24 -2
  11. package/dist/gateway/gateway-http-server.d.ts.map +1 -1
  12. package/dist/gateway/gateway-http-server.js +18 -1
  13. package/dist/gateway/gateway-http-server.js.map +1 -1
  14. package/dist/workspace.d.ts.map +1 -1
  15. package/dist/workspace.js +33 -8
  16. package/dist/workspace.js.map +1 -1
  17. package/docs/content/README.md +7 -1
  18. package/docs/content/channels/overview.md +27 -0
  19. package/docs/content/getting-started/installation.md +11 -7
  20. package/docs/content/getting-started/quickstart.md +136 -119
  21. package/docs/content/guides/bundled-skills.md +2 -2
  22. package/docs/index.html +21 -11
  23. package/npm-shrinkwrap.json +53 -61
  24. package/package.json +5 -5
  25. package/skills/langfuse/NOTICE.md +40 -0
  26. package/skills/langfuse/SKILL.md +287 -0
  27. package/skills/langfuse/evals/scenarios.json +83 -0
  28. package/skills/langfuse/langfuse.cjs +856 -0
  29. package/skills/langfuse/references/ci-cd.md +35 -0
  30. package/skills/langfuse/references/cli.md +59 -0
  31. package/skills/langfuse/references/error-analysis.md +107 -0
  32. package/skills/langfuse/references/instrumentation.md +134 -0
  33. package/skills/langfuse/references/judge-calibration.md +288 -0
  34. package/skills/langfuse/references/operator-setup.md +77 -0
  35. package/skills/langfuse/references/prompt-migration.md +234 -0
  36. package/skills/langfuse/references/sdk-upgrade.md +175 -0
  37. package/skills/langfuse/references/skill-feedback.md +52 -0
  38. package/skills/langfuse/references/user-feedback.md +88 -0
  39. package/skills/skill-creator/scripts/quick_validate.py +1 -0
package/AGENTS.md CHANGED
@@ -264,6 +264,13 @@ hybridclaw gateway status # gateway liveness, PID, build/version dia
264
264
  `await import()` and static `import` for the same module in production paths.
265
265
  - **Dependencies:** root `package.json` is for gateway/CLI deps. Container-only
266
266
  deps go in `container/package.json`. Never add container deps to root.
267
+ - When changing npm dependencies, update every generated dependency artifact in
268
+ the same change: the relevant `package-lock.json`, matching
269
+ `npm-shrinkwrap.json`, and the approved lockfile hashes in
270
+ `scripts/dependency-policy-baseline.json`. Use `npm run deps:update-lockfile`
271
+ when practical, or copy the updated lockfile to its shrinkwrap pair and
272
+ update the baseline hash after reviewing the lockfile diff. Run
273
+ `npm run deps:policy` before handing off.
267
274
 
268
275
  ### Git Discipline
269
276
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,44 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## [0.25.3](https://github.com/HybridAIOne/hybridclaw/tree/v0.25.3) - 2026-06-22
6
+
7
+ ### Added
8
+
9
+ - **Langfuse skill**: LLM observability and evaluation based on the official
10
+ Langfuse skill (`github.com/langfuse/skills`, MIT). Reads traces, observations,
11
+ sessions, scores, prompts, datasets, models, and metrics, and creates scores,
12
+ comments, datasets, dataset items, and prompt versions through the
13
+ gateway-proxied `langfuse.cjs` helper. Auth uses a SecretRef-backed
14
+ `Authorization: Basic <secret:LANGFUSE_BASIC_AUTH>` header with a
15
+ `LANGFUSE_HOST` config variable; reads are green and writes are grant-gated.
16
+ Bundles the upstream reference docs (instrumentation, prompt migration, error
17
+ analysis, judge calibration, SDK upgrade, CI/CD) plus Langfuse documentation
18
+ lookup (llms.txt / markdown / search-docs).
19
+
20
+ ### Changed
21
+
22
+ - **Quick Start guide**: Rewrote the getting-started quickstart into a
23
+ zero-to-working funnel -- a fast HybridAI Cloud path (model preselected,
24
+ already in web chat) and a numbered local path (onboard -> start gateway ->
25
+ confirm healthy with `gateway status` / `doctor` -> open chat -> send a first
26
+ message) with explicit success signals, a troubleshooting block, and a command
27
+ cheat sheet. Relocated the per-channel startup auto-connect conditions into the
28
+ Channels overview.
29
+ - **Apple desktop release**: README and docs point at the signed/notarized Apple
30
+ Silicon DMG, and the desktop wrapper captures gateway startup logs, recent
31
+ child output, spawn failures, and early exits so packaged app launch failures
32
+ are diagnosable.
33
+
34
+ ### Fixed
35
+
36
+ - **MCP server startup isolation**: A single MCP server that fails to connect or
37
+ disconnect is logged and skipped instead of aborting the whole chat turn, and
38
+ unchanged failed server configs are not retried every turn.
39
+ - **Empty heartbeat context**: Workspace bootstrap context skips the default
40
+ empty `HEARTBEAT.md` template and legacy "no recurring heartbeat tasks"
41
+ placeholders, avoiding noise in agent startup context.
42
+
5
43
  ## [0.25.2](https://github.com/HybridAIOne/hybridclaw/tree/v0.25.2) - 2026-06-20
6
44
 
7
45
  ### Fixed
@@ -13,6 +51,12 @@
13
51
 
14
52
  ## [0.25.1](https://github.com/HybridAIOne/hybridclaw/tree/v0.25.1) - 2026-06-20
15
53
 
54
+ ### Changed
55
+
56
+ - **Desktop packaging**: Desktop build commands rebuild the app before
57
+ packaging, reuse current icon/runtime stages, cache the staged Node runtime,
58
+ and strip non-runtime dependency files from packaged desktop bundles.
59
+
16
60
  ### Fixed
17
61
 
18
62
  - **HybridAI cloud admin sessions**: HybridAI-launched sessions without scoped
package/README.md CHANGED
@@ -69,7 +69,15 @@ HybridClaw on HybridAI Cloud in a few minutes at
69
69
 
70
70
  Fastest managed launch: [HybridClaw on HybridAI Cloud](https://hybridclaw.io).
71
71
 
72
- Linux/macOS one-line installer:
72
+ Apple Desktop App for macOS:
73
+
74
+ - Download the signed and notarized Apple Silicon DMG:
75
+ [HybridClaw-0.25.3-arm64.dmg](https://github.com/HybridAIOne/hybridclaw/releases/download/v0.25.3/HybridClaw-0.25.3-arm64.dmg)
76
+ - Open the DMG, drag `HybridClaw.app` into `/Applications`, and launch it.
77
+ - The desktop app starts the local gateway and opens the chat, agents, and
78
+ admin surfaces in a native macOS window.
79
+
80
+ Linux/macOS CLI one-line installer:
73
81
 
74
82
  ```bash
75
83
  curl -fsSL https://raw.githubusercontent.com/HybridAIOne/hybridclaw/main/scripts/install.sh | bash
@@ -98,7 +106,7 @@ After the gateway starts, open:
98
106
  | TUI | `hybridclaw tui` | Terminal chat, approvals, status, resume |
99
107
  | OpenAI-compatible API | `http://127.0.0.1:9090/v1/chat/completions` | Local evals and compatible clients |
100
108
 
101
- For signed macOS desktop builds, use the
109
+ For signed macOS desktop builds and future architectures, use the
102
110
  [GitHub Releases](https://github.com/HybridAIOne/hybridclaw/releases/latest)
103
111
  page.
104
112
 
@@ -220,7 +228,7 @@ Core pieces:
220
228
  | Build desktop releases | [Desktop Release Builds](https://hybridaione.github.io/hybridclaw/docs/developer-guide/desktop-release) |
221
229
  | Contribute | [CONTRIBUTING.md](./CONTRIBUTING.md), [docs/content/README.md](./docs/content/README.md) |
222
230
 
223
- Latest release: [v0.25.2](https://github.com/HybridAIOne/hybridclaw/releases/tag/v0.25.2).
231
+ Latest release: [v0.25.3](https://github.com/HybridAIOne/hybridclaw/releases/tag/v0.25.3).
224
232
  Release notes: [CHANGELOG.md](./CHANGELOG.md)
225
233
 
226
234
  ## Development
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hybridaione/hybridclaw-console",
3
3
  "private": true,
4
- "version": "0.25.2",
4
+ "version": "0.25.3",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsc --noEmit && vite build",
@@ -28,12 +28,28 @@ export class McpConfigWatcher {
28
28
  for (const name of Object.keys(previous)) {
29
29
  if (nextNames.has(name))
30
30
  continue;
31
- await this.manager.removeClient(name);
31
+ // Tearing down one server must not abort the whole apply.
32
+ try {
33
+ await this.manager.removeClient(name);
34
+ }
35
+ catch (error) {
36
+ this.logServerFailure('disconnect', name, error);
37
+ }
32
38
  }
33
39
  for (const [name, config] of Object.entries(nextConfig)) {
34
- if (!configsEqual(previous[name], config)) {
40
+ if (configsEqual(previous[name], config))
41
+ continue;
42
+ // A single MCP server failing to connect (e.g. an expired OAuth token
43
+ // answering the initial POST with 401/invalid_token) must NOT reject
44
+ // applyConfig — that rejection propagates up through syncMcpConfig into
45
+ // the chat turn and crashes it, taking down ALL chat for the agent.
46
+ // Log and skip the bad server so the turn proceeds with the rest.
47
+ try {
35
48
  await this.manager.replaceClient(name, config);
36
49
  }
50
+ catch (error) {
51
+ this.logServerFailure('connect', name, error);
52
+ }
37
53
  }
38
54
  this.lastConfig = nextConfig;
39
55
  this.lastHash = nextHash;
@@ -43,5 +59,9 @@ export class McpConfigWatcher {
43
59
  this.lastConfig = {};
44
60
  this.lastHash = stableHash('{}');
45
61
  }
62
+ logServerFailure(phase, name, error) {
63
+ const detail = error instanceof Error ? error.message : String(error);
64
+ console.error(`[mcp:${name}] failed to ${phase}: ${detail}`);
65
+ }
46
66
  }
47
67
  //# sourceMappingURL=config-watcher.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-watcher.js","sourceRoot":"","sources":["../../src/mcp/config-watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAKzC,SAAS,WAAW,CAClB,OAAoD;IAEpD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC,CAG9C,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,YAAY,CACnB,IAAiC,EACjC,KAAsB;IAEtB,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,OAAO,gBAAgB;IAIE;IAHrB,UAAU,GAAoC,EAAE,CAAC;IACjD,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAEpC,YAA6B,OAAyB;QAAzB,YAAO,GAAP,OAAO,CAAkB;IAAG,CAAC;IAE1D,KAAK,CAAC,KAAK,CAAC,OAAyC;QACnD,OAAO,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,WAAW,CACf,OAAyC;QAEzC,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;QACxD,IAAI,QAAQ,KAAK,IAAI,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QAE7C,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC;QACjC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAEnD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACzC,IAAI,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,SAAS;YAClC,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACxC,CAAC;QAED,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YACxD,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC;gBAC1C,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACjD,CAAC;QACH,CAAC;QAED,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI;QACF,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;QACrB,IAAI,CAAC,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;CACF"}
1
+ {"version":3,"file":"config-watcher.js","sourceRoot":"","sources":["../../src/mcp/config-watcher.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "hybridclaw-agent",
3
- "version": "0.25.2",
3
+ "version": "0.25.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "hybridclaw-agent",
9
- "version": "0.25.2",
9
+ "version": "0.25.3",
10
10
  "dependencies": {
11
11
  "@modelcontextprotocol/sdk": "1.29.0",
12
12
  "@mozilla/readability": "0.6.0",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "hybridclaw-agent",
3
- "version": "0.25.2",
3
+ "version": "0.25.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "hybridclaw-agent",
9
- "version": "0.25.2",
9
+ "version": "0.25.3",
10
10
  "dependencies": {
11
11
  "@modelcontextprotocol/sdk": "1.29.0",
12
12
  "@mozilla/readability": "0.6.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hybridclaw-agent",
3
- "version": "0.25.2",
3
+ "version": "0.25.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "packageManager": "npm@11.10.0",
@@ -45,12 +45,25 @@ export class McpConfigWatcher {
45
45
 
46
46
  for (const name of Object.keys(previous)) {
47
47
  if (nextNames.has(name)) continue;
48
- await this.manager.removeClient(name);
48
+ // Tearing down one server must not abort the whole apply.
49
+ try {
50
+ await this.manager.removeClient(name);
51
+ } catch (error) {
52
+ this.logServerFailure('disconnect', name, error);
53
+ }
49
54
  }
50
55
 
51
56
  for (const [name, config] of Object.entries(nextConfig)) {
52
- if (!configsEqual(previous[name], config)) {
57
+ if (configsEqual(previous[name], config)) continue;
58
+ // A single MCP server failing to connect (e.g. an expired OAuth token
59
+ // answering the initial POST with 401/invalid_token) must NOT reject
60
+ // applyConfig — that rejection propagates up through syncMcpConfig into
61
+ // the chat turn and crashes it, taking down ALL chat for the agent.
62
+ // Log and skip the bad server so the turn proceeds with the rest.
63
+ try {
53
64
  await this.manager.replaceClient(name, config);
65
+ } catch (error) {
66
+ this.logServerFailure('connect', name, error);
54
67
  }
55
68
  }
56
69
 
@@ -63,4 +76,13 @@ export class McpConfigWatcher {
63
76
  this.lastConfig = {};
64
77
  this.lastHash = stableHash('{}');
65
78
  }
79
+
80
+ private logServerFailure(
81
+ phase: 'connect' | 'disconnect',
82
+ name: string,
83
+ error: unknown,
84
+ ): void {
85
+ const detail = error instanceof Error ? error.message : String(error);
86
+ console.error(`[mcp:${name}] failed to ${phase}: ${detail}`);
87
+ }
66
88
  }
@@ -1 +1 @@
1
- {"version":3,"file":"gateway-http-server.d.ts","sourceRoot":"","sources":["../../src/gateway/gateway-http-server.ts"],"names":[],"mappings":"AA8zNA,MAAM,WAAW,iBAAiB;IAChC,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wBAAgB,sBAAsB,IAAI,iBAAiB,CAk3B1D"}
1
+ {"version":3,"file":"gateway-http-server.d.ts","sourceRoot":"","sources":["../../src/gateway/gateway-http-server.ts"],"names":[],"mappings":"AA40NA,MAAM,WAAW,iBAAiB;IAChC,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wBAAgB,sBAAsB,IAAI,iBAAiB,CAw3B1D"}
@@ -1584,6 +1584,17 @@ function resolveRequestOrigin(req, bodyBaseUrl) {
1584
1584
  const host = forwardedHost || req.headers.host || `127.0.0.1:${HEALTH_PORT}`;
1585
1585
  return `${proto}://${host}`;
1586
1586
  }
1587
+ function resolveA2AAgentCardOrigin(req) {
1588
+ const configuredPublicUrl = getRuntimeConfig().deployment.public_url.trim();
1589
+ if (configuredPublicUrl) {
1590
+ const origin = normalizePublicBaseUrl(configuredPublicUrl);
1591
+ if (origin)
1592
+ return origin;
1593
+ logger.warn({ publicUrl: configuredPublicUrl }, 'Invalid deployment.public_url for A2A Agent Card');
1594
+ return null;
1595
+ }
1596
+ return resolveRequestOrigin(req);
1597
+ }
1587
1598
  function buildMobileLaunchUrl(params) {
1588
1599
  const url = new URL('/chat/continue', params.origin);
1589
1600
  url.searchParams.set('token', params.token);
@@ -5057,7 +5068,13 @@ export function startGatewayHttpServer() {
5057
5068
  }
5058
5069
  const voicePaths = resolveVoiceWebhookPaths(getRuntimeConfig().voice.webhookPath);
5059
5070
  if (pathname === '/.well-known/agent.json' && method === 'GET') {
5060
- const origin = resolveRequestOrigin(req);
5071
+ const origin = resolveA2AAgentCardOrigin(req);
5072
+ if (!origin) {
5073
+ sendJson(res, 500, {
5074
+ error: 'deployment.public_url must be an HTTP(S) URL.',
5075
+ });
5076
+ return;
5077
+ }
5061
5078
  const trust = resolveA2AAgentCardPeerTrust({
5062
5079
  authorization: req.headers.authorization || '',
5063
5080
  audience: new URL('/.well-known/agent.json', origin).toString(),