chainlesschain 0.166.131 → 0.166.133

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 (32) hide show
  1. package/README.md +46 -18
  2. package/package.json +1 -1
  3. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  4. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  5. package/src/data/changelog.json +26 -0
  6. package/src/gateways/ws/llm-chat-protocol.js +1 -1
  7. package/src/gateways/ws/personal-data-hub-protocol.js +27 -11
  8. package/src/gateways/ws/session-core-protocol.js +22 -17
  9. package/src/gateways/ws/session-protocol.js +19 -8
  10. package/src/gateways/ws/ws-agent-handler.js +30 -0
  11. package/src/lib/chat-core.js +40 -35
  12. package/src/lib/context-memory-kernel/message-adapter.js +27 -6
  13. package/src/lib/context-memory-kernel/provider-context.js +48 -3
  14. package/src/lib/evolution/agent-model-projection.js +0 -1
  15. package/src/lib/evolution/governed-hub-llm.js +5 -1
  16. package/src/lib/evolution/governed-hub-resolver.js +15 -6
  17. package/src/lib/evolution/governed-hub-skill.js +2 -0
  18. package/src/lib/evolution/optional-ui-model-turn.js +13 -2
  19. package/src/lib/llm-config-defaults.js +17 -5
  20. package/src/lib/llm-connection-config.js +10 -2
  21. package/src/lib/llm-connection-probe.js +5 -2
  22. package/src/lib/llm-connection-resolution.js +7 -4
  23. package/src/lib/llm-providers.js +24 -11
  24. package/src/lib/openai-responses.js +2 -1
  25. package/src/lib/personal-data-hub-wiring.js +69 -36
  26. package/src/lib/provider-protocol.js +35 -3
  27. package/src/lib/provider-stream.js +6 -4
  28. package/src/lib/runnable-provider.js +4 -0
  29. package/src/lib/ws-chat-handler.js +22 -9
  30. package/src/runtime/agent-core.js +201 -15
  31. package/src/runtime/message-roles.js +67 -3
  32. package/src/runtime/provider-stream-state.js +35 -1
@@ -644,35 +644,44 @@ async function initHub() {
644
644
  // collector can pull WebView cookies from the user's Android Bilibili
645
645
  // App. The factory is a pure function — no side effects until the
646
646
  // handler is invoked, so cost of always-registering is zero.
647
- const { createBilibiliCookiesExtension } =
648
- await import("@chainlesschain/personal-data-hub/adapters/social-bilibili-adb");
647
+ const { createBilibiliCookiesExtension } = await import(
648
+ "@chainlesschain/personal-data-hub/adapters/social-bilibili-adb"
649
+ );
649
650
  // Phase 2a: register `douyin.pull-im-db` extension so the
650
651
  // douyinAdbSync hub method can pull <uid>_im.db cohort from the
651
652
  // user's Android Douyin App.
652
653
  const { createDouyinDbExtension, createDouyinWatchExtension } =
653
- await import("@chainlesschain/personal-data-hub/adapters/social-douyin-adb");
654
+ await import(
655
+ "@chainlesschain/personal-data-hub/adapters/social-douyin-adb"
656
+ );
654
657
  // Phase 3a: register `weibo.cookies` extension for the Weibo
655
658
  // C-path collector (m.weibo.cn cookies + 4 HTTP endpoints).
656
- const { createWeiboCookiesExtension } =
657
- await import("@chainlesschain/personal-data-hub/adapters/social-weibo-adb");
659
+ const { createWeiboCookiesExtension } = await import(
660
+ "@chainlesschain/personal-data-hub/adapters/social-weibo-adb"
661
+ );
658
662
  // Phase 3c: register `xhs.cookies` extension for the Xhs C-path
659
663
  // collector (xiaohongshu.com cookies + 4 endpoints with X-S sign).
660
- const { createXhsCookiesExtension } =
661
- await import("@chainlesschain/personal-data-hub/adapters/social-xiaohongshu-adb");
664
+ const { createXhsCookiesExtension } = await import(
665
+ "@chainlesschain/personal-data-hub/adapters/social-xiaohongshu-adb"
666
+ );
662
667
  // Phase 6c: register `toutiao.cookies` extension for the Toutiao
663
668
  // C-path collector (www.toutiao.com cookies + 4 endpoints, 3 of
664
669
  // which need _signature signing — CLI context falls back to -99
665
670
  // short-circuit, desktop wiring upgrades via ToutiaoSignBridge).
666
671
  const { createToutiaoCookiesExtension, createToutiaoAccountExtension } =
667
- await import("@chainlesschain/personal-data-hub/adapters/social-toutiao-adb");
672
+ await import(
673
+ "@chainlesschain/personal-data-hub/adapters/social-toutiao-adb"
674
+ );
668
675
  // Phase 6d: register `kuaishou.cookies` extension. Profile from
669
676
  // cookie's api_ph payload (no HTTP); 3 GraphQL POST endpoints need
670
677
  // __NS_sig3 + kpf/kpn — CLI short-circuits, desktop upgrades via
671
678
  // KuaishouSignBridge.
672
- const { createKuaishouCookiesExtension } =
673
- await import("@chainlesschain/personal-data-hub/adapters/social-kuaishou-adb");
674
- const { createWhatsAppBackupExtension } =
675
- await import("@chainlesschain/personal-data-hub/adapters/messaging-whatsapp");
679
+ const { createKuaishouCookiesExtension } = await import(
680
+ "@chainlesschain/personal-data-hub/adapters/social-kuaishou-adb"
681
+ );
682
+ const { createWhatsAppBackupExtension } = await import(
683
+ "@chainlesschain/personal-data-hub/adapters/messaging-whatsapp"
684
+ );
676
685
  hostAdbBridge = createHostAdbBridge({
677
686
  extensions: {
678
687
  "bilibili.cookies": createBilibiliCookiesExtension(),
@@ -1105,16 +1114,20 @@ async function initHub() {
1105
1114
  alipayAccountsPath,
1106
1115
  entityResolver,
1107
1116
  aichatAccountsStore,
1108
- async drainResolver(options = {}, factory = null) {
1109
- if (typeof factory !== "function") {
1117
+ async drainResolver(options = {}, factory = null, modelTurnOptions = {}) {
1118
+ if (
1119
+ typeof factory !== "function" &&
1120
+ !(factory === null && modelTurnOptions.evolutionDisabled === true)
1121
+ ) {
1110
1122
  const error = new Error(
1111
1123
  "Personal Data Hub resolver egress requires an authenticated evolution composition factory",
1112
1124
  );
1113
1125
  error.code = "CC_AGENT_EVOLUTION_INGRESS_FAILED";
1114
1126
  throw error;
1115
1127
  }
1116
- const { createGovernedHubResolver } =
1117
- await import("./evolution/governed-hub-resolver.js");
1128
+ const { createGovernedHubResolver } = await import(
1129
+ "./evolution/governed-hub-resolver.js"
1130
+ );
1118
1131
  const scoped = createGovernedHubResolver(
1119
1132
  {
1120
1133
  resolver: entityResolver,
@@ -1125,6 +1138,8 @@ async function initHub() {
1125
1138
  LLMStage: EntityResolverLLMStage,
1126
1139
  },
1127
1140
  factory,
1141
+ undefined,
1142
+ modelTurnOptions,
1128
1143
  );
1129
1144
  return scoped.drain(options);
1130
1145
  },
@@ -1656,8 +1671,9 @@ async function initHub() {
1656
1671
  }
1657
1672
  let collector;
1658
1673
  try {
1659
- const mod =
1660
- await import("@chainlesschain/personal-data-hub/adapters/social-bilibili-adb");
1674
+ const mod = await import(
1675
+ "@chainlesschain/personal-data-hub/adapters/social-bilibili-adb"
1676
+ );
1661
1677
  collector = mod.default ? mod.default : mod;
1662
1678
  } catch (err) {
1663
1679
  return {
@@ -1708,8 +1724,9 @@ async function initHub() {
1708
1724
  }
1709
1725
  let collector;
1710
1726
  try {
1711
- const mod =
1712
- await import("@chainlesschain/personal-data-hub/adapters/social-douyin-adb");
1727
+ const mod = await import(
1728
+ "@chainlesschain/personal-data-hub/adapters/social-douyin-adb"
1729
+ );
1713
1730
  collector = mod.default ? mod.default : mod;
1714
1731
  } catch (err) {
1715
1732
  return {
@@ -1752,8 +1769,9 @@ async function initHub() {
1752
1769
  }
1753
1770
  let collector;
1754
1771
  try {
1755
- const mod =
1756
- await import("@chainlesschain/personal-data-hub/adapters/social-douyin-adb");
1772
+ const mod = await import(
1773
+ "@chainlesschain/personal-data-hub/adapters/social-douyin-adb"
1774
+ );
1757
1775
  collector = mod.default ? mod.default : mod;
1758
1776
  } catch (err) {
1759
1777
  return {
@@ -1799,8 +1817,9 @@ async function initHub() {
1799
1817
  }
1800
1818
  let collector;
1801
1819
  try {
1802
- const mod =
1803
- await import("@chainlesschain/personal-data-hub/adapters/social-weibo-adb");
1820
+ const mod = await import(
1821
+ "@chainlesschain/personal-data-hub/adapters/social-weibo-adb"
1822
+ );
1804
1823
  collector = mod.default ? mod.default : mod;
1805
1824
  } catch (err) {
1806
1825
  return {
@@ -1852,8 +1871,9 @@ async function initHub() {
1852
1871
  }
1853
1872
  let collector;
1854
1873
  try {
1855
- const mod =
1856
- await import("@chainlesschain/personal-data-hub/adapters/social-xiaohongshu-adb");
1874
+ const mod = await import(
1875
+ "@chainlesschain/personal-data-hub/adapters/social-xiaohongshu-adb"
1876
+ );
1857
1877
  collector = mod.default ? mod.default : mod;
1858
1878
  } catch (err) {
1859
1879
  return {
@@ -1905,8 +1925,9 @@ async function initHub() {
1905
1925
  }
1906
1926
  let collector;
1907
1927
  try {
1908
- const mod =
1909
- await import("@chainlesschain/personal-data-hub/adapters/social-toutiao-adb");
1928
+ const mod = await import(
1929
+ "@chainlesschain/personal-data-hub/adapters/social-toutiao-adb"
1930
+ );
1910
1931
  collector = mod.default ? mod.default : mod;
1911
1932
  } catch (err) {
1912
1933
  return {
@@ -1960,8 +1981,9 @@ async function initHub() {
1960
1981
  }
1961
1982
  let collector;
1962
1983
  try {
1963
- const mod =
1964
- await import("@chainlesschain/personal-data-hub/adapters/social-kuaishou-adb");
1984
+ const mod = await import(
1985
+ "@chainlesschain/personal-data-hub/adapters/social-kuaishou-adb"
1986
+ );
1965
1987
  collector = mod.default ? mod.default : mod;
1966
1988
  } catch (err) {
1967
1989
  return {
@@ -2149,11 +2171,19 @@ export async function getHub() {
2149
2171
  return _initPromise;
2150
2172
  }
2151
2173
 
2152
- export async function getGovernedAnalysisHub(evolutionCompositionFactory) {
2174
+ export async function getGovernedAnalysisHub(
2175
+ evolutionCompositionFactory,
2176
+ modelTurnOptions = {},
2177
+ ) {
2153
2178
  const hub = await getHub();
2154
- const { createGovernedHubLlm } =
2155
- await import("./evolution/governed-hub-llm.js");
2156
- const llm = createGovernedHubLlm(hub.engine.llm, evolutionCompositionFactory);
2179
+ const { createGovernedHubLlm } = await import(
2180
+ "./evolution/governed-hub-llm.js"
2181
+ );
2182
+ const llm = createGovernedHubLlm(
2183
+ hub.engine.llm,
2184
+ evolutionCompositionFactory,
2185
+ modelTurnOptions,
2186
+ );
2157
2187
  const engine = new AnalysisEngine({
2158
2188
  vault: hub.engine.vault,
2159
2189
  llm,
@@ -2162,8 +2192,9 @@ export async function getGovernedAnalysisHub(evolutionCompositionFactory) {
2162
2192
  maxQueryLimit: hub.engine.maxQueryLimit,
2163
2193
  systemPrompt: hub.engine.systemPrompt,
2164
2194
  });
2165
- const { runGovernedHubSkill } =
2166
- await import("./evolution/governed-hub-skill.js");
2195
+ const { runGovernedHubSkill } = await import(
2196
+ "./evolution/governed-hub-skill.js"
2197
+ );
2167
2198
  return Object.freeze({
2168
2199
  ...hub,
2169
2200
  llm,
@@ -2175,6 +2206,8 @@ export async function getGovernedAnalysisHub(evolutionCompositionFactory) {
2175
2206
  runAnalysisSkill,
2176
2207
  name,
2177
2208
  options,
2209
+ undefined,
2210
+ modelTurnOptions,
2178
2211
  ),
2179
2212
  });
2180
2213
  }
@@ -4,6 +4,29 @@ export function normalizeProviderBaseUrl(baseUrl) {
4
4
  return String(baseUrl || "").replace(/\/+$/u, "");
5
5
  }
6
6
 
7
+ /** Validate custom targets without assuming a vendor, path, model, or key. */
8
+ export function assertCustomProviderConnection(provider, { baseUrl, model }) {
9
+ if (provider !== "custom") return;
10
+ let url;
11
+ try {
12
+ url = new URL(baseUrl);
13
+ } catch {
14
+ throw new Error("Custom provider requires an HTTP(S) API base URL");
15
+ }
16
+ if (
17
+ !["http:", "https:"].includes(url.protocol) ||
18
+ url.username ||
19
+ url.password ||
20
+ url.search ||
21
+ url.hash
22
+ )
23
+ throw new Error(
24
+ "Custom provider requires an HTTP(S) API base URL without credentials, query or fragment",
25
+ );
26
+ if (typeof model !== "string" || !model.trim())
27
+ throw new Error("Custom provider requires an explicit model name");
28
+ }
29
+
7
30
  /**
8
31
  * Google's OpenAI-compatible API lives below `/v1beta/openai`. The provider
9
32
  * registry stores the native API root (`/v1beta`), so adapt only that exact
@@ -28,7 +51,11 @@ export function openAICompatibleBaseUrl(provider, baseUrl) {
28
51
  return base;
29
52
  }
30
53
 
31
- export function geminiGenerateContentUrl(baseUrl, model, { stream = false } = {}) {
54
+ export function geminiGenerateContentUrl(
55
+ baseUrl,
56
+ model,
57
+ { stream = false } = {},
58
+ ) {
32
59
  const action = stream ? "streamGenerateContent" : "generateContent";
33
60
  const endpoint = `${normalizeProviderBaseUrl(baseUrl)}/models/${encodeURIComponent(model)}:${action}`;
34
61
  return stream ? `${endpoint}?alt=sse` : endpoint;
@@ -58,7 +85,10 @@ export function geminiRequestBody(
58
85
  continue;
59
86
  }
60
87
  contents.push({
61
- role: message.role === "assistant" || message.role === "model" ? "model" : "user",
88
+ role:
89
+ message.role === "assistant" || message.role === "model"
90
+ ? "model"
91
+ : "user",
62
92
  parts,
63
93
  });
64
94
  }
@@ -66,7 +96,9 @@ export function geminiRequestBody(
66
96
  if (Number.isFinite(temperature)) generationConfig.temperature = temperature;
67
97
  return {
68
98
  contents,
69
- ...(systemParts.length ? { systemInstruction: { parts: systemParts } } : {}),
99
+ ...(systemParts.length
100
+ ? { systemInstruction: { parts: systemParts } }
101
+ : {}),
70
102
  generationConfig,
71
103
  };
72
104
  }
@@ -8,10 +8,11 @@ import {
8
8
  * of token deltas suitable for piping through session-core StreamRouter.
9
9
  */
10
10
 
11
- import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
11
+ import { resolveProviderDefinition } from "./llm-providers.js";
12
12
  import { normalizeOllamaBaseUrl } from "./ollama-base-url.js";
13
13
  import { ollamaProviderFetch } from "./ollama-provider-fetch.js";
14
14
  import {
15
+ assertCustomProviderConnection,
15
16
  geminiGenerateContentUrl,
16
17
  geminiRequestBody,
17
18
  normalizeProviderBaseUrl,
@@ -111,7 +112,7 @@ export async function* openAIStream({
111
112
  method: "POST",
112
113
  headers: {
113
114
  "Content-Type": "application/json",
114
- Authorization: `Bearer ${apiKey}`,
115
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
115
116
  },
116
117
  body: JSON.stringify({
117
118
  model,
@@ -276,6 +277,7 @@ export async function* anthropicTokenStream({
276
277
  * Throws on unsupported provider / missing API key.
277
278
  */
278
279
  export function buildProviderSource(provider, opts = {}) {
280
+ assertCustomProviderConnection(provider, opts);
279
281
  const {
280
282
  model,
281
283
  baseUrl,
@@ -295,11 +297,11 @@ export function buildProviderSource(provider, opts = {}) {
295
297
  requireCompletion,
296
298
  });
297
299
  }
298
- const def = BUILT_IN_PROVIDERS[provider];
300
+ const def = resolveProviderDefinition(provider);
299
301
  if (!def) throw new Error(`Unsupported provider: ${provider}`);
300
302
  const finalKey =
301
303
  apiKey || (def.apiKeyEnv ? process.env[def.apiKeyEnv] : null);
302
- if (!finalKey) {
304
+ if (!finalKey && provider !== "custom") {
303
305
  throw new Error(
304
306
  `API key required for ${provider} (--api-key or ${def.apiKeyEnv})`,
305
307
  );
@@ -146,6 +146,8 @@ export function makeRunnableProviderFallback(
146
146
  // react to an auth failure AND pre-empt the known-bad attempt on later turns.
147
147
  const computeFallback = (options) => {
148
148
  const failed = options.provider;
149
+ // A custom protocol selection is intentional, including on localhost.
150
+ if (failed === "custom") return null;
149
151
  const inferred = inferProviderFromBaseUrl(options.baseUrl);
150
152
  if (inferred && inferred !== failed) {
151
153
  return {
@@ -269,6 +271,8 @@ export function hasUsableKey(
269
271
  provider,
270
272
  { apiKey, isActive = true, env = process.env } = {},
271
273
  ) {
274
+ // Custom platforms may be keyless; connection validation checks their target.
275
+ if (provider === "custom") return true;
272
276
  const def = BUILT_IN_PROVIDERS[provider];
273
277
  if (!def) return false;
274
278
  if (!def.apiKeyEnv) return true; // keyless local provider (e.g. ollama)
@@ -14,7 +14,12 @@ export class WSChatHandler {
14
14
  * @param {import("./ws-session-manager.js").Session} options.session
15
15
  * @param {import("./interaction-adapter.js").WebSocketInteractionAdapter} options.interaction
16
16
  */
17
- constructor({ session, interaction, evolutionCompositionFactory = null }) {
17
+ constructor({
18
+ session,
19
+ interaction,
20
+ evolutionCompositionFactory = null,
21
+ prepareUiModelTurn = null,
22
+ }) {
18
23
  if (
19
24
  evolutionCompositionFactory !== null &&
20
25
  typeof evolutionCompositionFactory !== "function"
@@ -22,6 +27,9 @@ export class WSChatHandler {
22
27
  throw new TypeError("evolutionCompositionFactory must be a function");
23
28
  }
24
29
  this._evolutionCompositionFactory = evolutionCompositionFactory;
30
+ if (prepareUiModelTurn !== null && typeof prepareUiModelTurn !== "function")
31
+ throw new TypeError("prepareUiModelTurn must be a function");
32
+ this._prepareUiModelTurn = prepareUiModelTurn;
25
33
  this.session = session;
26
34
  this.interaction = interaction;
27
35
  this._processing = false;
@@ -64,17 +72,22 @@ export class WSChatHandler {
64
72
  });
65
73
  runRecorded = true;
66
74
 
67
- const turn = await prepareGovernedModelTurn(
68
- this._evolutionCompositionFactory,
69
- {
70
- mode: "ws-chat-session",
71
- messages: session.messages,
72
- },
73
- );
75
+ const turn = await (
76
+ this._prepareUiModelTurn ||
77
+ ((context) =>
78
+ prepareGovernedModelTurn(this._evolutionCompositionFactory, context))
79
+ )({
80
+ mode: "ws-chat-session",
81
+ messages: session.messages,
82
+ });
74
83
  const options = {
75
84
  provider: session.provider,
76
85
  model: session.model,
77
- baseUrl: session.baseUrl || "http://localhost:11434",
86
+ baseUrl:
87
+ session.baseUrl ||
88
+ (session.provider === "ollama"
89
+ ? "http://localhost:11434"
90
+ : undefined),
78
91
  apiKey: session.apiKey,
79
92
  // Phase J — pipe WS session id so chat-core records token_usage
80
93
  // into the JSONL session store; visible via `cc session usage`.