@parall/parall 1.33.0 → 1.35.0

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.
package/dist/gateway.d.ts CHANGED
@@ -1,7 +1,16 @@
1
+ import type { PluginRuntime } from 'openclaw/plugin-sdk';
1
2
  import type { ChannelPlugin } from 'openclaw/plugin-sdk/core';
2
3
  /** Extract ChannelGatewayAdapter from ChannelPlugin (removed from public SDK exports in 2026.3.24). */
3
4
  type ChannelGatewayAdapter<T = unknown> = NonNullable<ChannelPlugin<T>['gateway']>;
5
+ import { type DispatchAdapter } from '@parall/agent-core';
4
6
  import type { ResolvedParallAccount } from './types.js';
7
+ export declare function createOpenClawDispatchAdapter(opts: {
8
+ core: PluginRuntime;
9
+ cfg: Record<string, unknown>;
10
+ accountId: string;
11
+ sessionsDir: string;
12
+ workspaceDir: string;
13
+ }): DispatchAdapter;
5
14
  export declare const parallGateway: ChannelGatewayAdapter<ResolvedParallAccount>;
6
15
  export {};
7
16
  //# sourceMappingURL=gateway.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,uGAAuG;AACvG,KAAK,qBAAqB,CAAC,CAAC,GAAG,OAAO,IAAI,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AAgCnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AA8SxD,eAAO,MAAM,aAAa,EAAE,qBAAqB,CAAC,qBAAqB,CAmJtE,CAAC"}
1
+ {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,uGAAuG;AACvG,KAAK,qBAAqB,CAAC,CAAC,GAAG,OAAO,IAAI,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AACnF,OAAO,EAOL,KAAK,eAAe,EAGrB,MAAM,oBAAoB,CAAC;AAqB5B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAiKxD,wBAAgB,6BAA6B,CAAC,IAAI,EAAE;IAClD,IAAI,EAAE,aAAa,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,eAAe,CA0KlB;AAED,eAAO,MAAM,aAAa,EAAE,qBAAqB,CAAC,qBAAqB,CAmJtE,CAAC"}
package/dist/gateway.js CHANGED
@@ -145,8 +145,41 @@ async function waitForOpenClawSessionId(sessionsDir, sessionKey, timeoutMs = 10_
145
145
  }
146
146
  return null;
147
147
  }
148
- function createOpenClawDispatchAdapter(opts) {
148
+ export function createOpenClawDispatchAdapter(opts) {
149
149
  const activeStreams = new Map();
150
+ let configFallbackWarned = false;
151
+ /**
152
+ * Resolve the config for one dispatch. OpenClaw hot-reloads
153
+ * `agents.defaults.model` / `models.providers` WITHOUT restarting channel
154
+ * plugins, so the `ctx.cfg` snapshot captured at startAccount goes stale the
155
+ * moment the operator switches the agent's model — every run would keep the
156
+ * old model until the whole gateway restarts. `core.config.current()` is the
157
+ * host's live (hot-reload-refreshed) config; fall back to the startup
158
+ * snapshot on hosts that predate the API. The fallback warns once per
159
+ * degradation streak (latch resets on success) — silently dispatching with
160
+ * the stale snapshot would recreate the invisible model-switch mismatch this
161
+ * function exists to fix.
162
+ */
163
+ function resolveDispatchConfig(log) {
164
+ try {
165
+ const current = opts.core.config?.current?.();
166
+ if (current && typeof current === 'object') {
167
+ configFallbackWarned = false;
168
+ return current;
169
+ }
170
+ if (!configFallbackWarned) {
171
+ configFallbackWarned = true;
172
+ log?.warn?.(`parall[${opts.accountId}]: config.current() unavailable on this host — dispatching with the startup config snapshot; model switches need a gateway restart until then`);
173
+ }
174
+ }
175
+ catch (err) {
176
+ if (!configFallbackWarned) {
177
+ configFallbackWarned = true;
178
+ log?.warn?.(`parall[${opts.accountId}]: config.current() failed (${String(err)}) — dispatching with the startup config snapshot; model switches need a gateway restart until then`);
179
+ }
180
+ }
181
+ return opts.cfg;
182
+ }
150
183
  return {
151
184
  abortDispatch(sessionKey) {
152
185
  activeStreams.get(sessionKey)?.fail(new Error('dispatch deadline exceeded'));
@@ -172,7 +205,7 @@ function createOpenClawDispatchAdapter(opts) {
172
205
  let turnGroupKey = '';
173
206
  const run = opts.core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
174
207
  ctx: buildInboundCtx(opts.core, opts.accountId, event, sessionKey, promptBody, earlierEvents),
175
- cfg: opts.cfg,
208
+ cfg: resolveDispatchConfig(context.log),
176
209
  dispatcherOptions: {
177
210
  deliver: async (payload) => {
178
211
  const replyText = payload.text?.trim();
@@ -27010,6 +27010,7 @@ var ENDPOINTS = {
27010
27010
  // Org-scoped
27011
27011
  ORG: (orgId) => `${API_BASE}/orgs/${orgId}`,
27012
27012
  ORG_MEMBERS: (orgId) => `${API_BASE}/orgs/${orgId}/members`,
27013
+ TEAMS: (orgId) => `${API_BASE}/orgs/${orgId}/teams`,
27013
27014
  ORG_MEMBERS_ONLINE: (orgId) => `${API_BASE}/orgs/${orgId}/members/online`,
27014
27015
  ORG_MEMBER: (orgId, userId) => `${API_BASE}/orgs/${orgId}/members/${userId}`,
27015
27016
  ORG_MEMBER_CHATS: (orgId, memberId) => `${API_BASE}/orgs/${orgId}/members/${memberId}/chats`,
@@ -27054,6 +27055,7 @@ var ENDPOINTS = {
27054
27055
  AGENT: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}`,
27055
27056
  AGENT_API_KEYS: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/api-keys`,
27056
27057
  AGENT_API_KEY: (orgId, agentId, key) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/api-keys/${key}`,
27058
+ AGENT_API_KEY_REGENERATE: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/api-keys/regenerate`,
27057
27059
  AGENT_AVATAR: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/avatar`,
27058
27060
  AGENT_ACTIVITY: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/activity`,
27059
27061
  AGENT_MONITOR: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/monitor`,
@@ -27093,6 +27095,7 @@ var ENDPOINTS = {
27093
27095
  MACHINE_AGENT_WORKSPACE_SETUP: (orgId, machineId, agentId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/agents/${agentId}/workspace/setup`,
27094
27096
  MACHINE_LLM_SOURCE: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/llm-source`,
27095
27097
  MACHINE_PROVIDER_ENABLED: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/provider-enabled`,
27098
+ MACHINE_CAPABILITIES: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/capabilities`,
27096
27099
  MACHINE_RUNTIME_AUTH: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/runtime-auth`,
27097
27100
  MACHINE_KEYS: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/keys`,
27098
27101
  MACHINE_KEY: (orgId, machineId, keyId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/keys/${keyId}`,
@@ -27113,6 +27116,12 @@ var ENDPOINTS = {
27113
27116
  MACHINES_ME_AGENT_WORKSPACE_STATE: (agentId) => `${API_BASE}/machines/me/agents/${agentId}/workspace-state`,
27114
27117
  MACHINES_ME_WS_TICKET: `${API_BASE}/machines/me/ws/ticket`,
27115
27118
  MACHINES_ME_BROWSE_RESPONSE: (requestId) => `${API_BASE}/machines/me/browse-response/${requestId}`,
27119
+ // Hosted browser live-viewer control plane (api-server, NOT clip-service):
27120
+ // the web client drives WebRTC signaling + tab nav through VIEWER_COMMAND;
27121
+ // api-server brokers each command to the host daemon via the machine:{id}
27122
+ // request/reply bridge (mirrors filesystem browse), and the daemon replies on
27123
+ // VIEWER_RESPONSE. See docs/engineering-design/hosted-browser-provider-design.md.
27124
+ MACHINES_ME_BROWSER_PROFILE_VIEWER_RESPONSE: (requestId) => `${API_BASE}/machines/me/browser-profiles/viewer-response/${requestId}`,
27116
27125
  // Tasks (org-scoped)
27117
27126
  TASKS: (orgId) => `${API_BASE}/orgs/${orgId}/tasks`,
27118
27127
  TASK: (orgId, taskId) => `${API_BASE}/orgs/${orgId}/tasks/${taskId}`,
@@ -27178,6 +27187,9 @@ var ENDPOINTS = {
27178
27187
  // Wiki Path Scopes (AFCS ACL)
27179
27188
  WIKI_PATH_SCOPES: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/path-scopes`,
27180
27189
  WIKI_PATH_SCOPE: (orgId, wikiId, scopeId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/path-scopes/${scopeId}`,
27190
+ // Wiki Path Restrictions (AFCS narrowing ACL — private subtrees)
27191
+ WIKI_RESTRICTIONS: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/restrictions`,
27192
+ WIKI_RESTRICTION: (orgId, wikiId, restrictionId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/restrictions/${restrictionId}`,
27181
27193
  WIKI_ACCESS_STATUS: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/access-status`,
27182
27194
  WIKI_ACCESS_REQUESTS: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/access-requests`,
27183
27195
  // Wiki History (commits, file commits, blame)
@@ -27226,6 +27238,8 @@ var ENDPOINTS = {
27226
27238
  PUSH_VAPID_KEY: `${API_BASE}/push/vapid-key`,
27227
27239
  // Notification preferences
27228
27240
  NOTIFICATION_PREFERENCES: `${API_BASE}/notification-preferences`,
27241
+ // Unified search (messages + tasks + wiki, org-scoped)
27242
+ SEARCH: (orgId) => `${API_BASE}/orgs/${orgId}/search`,
27229
27243
  // Feature flags (org-scoped, server-evaluated)
27230
27244
  FEATURE_FLAGS: (orgId) => `${API_BASE}/orgs/${orgId}/feature-flags`,
27231
27245
  // Billing & Credits (org-scoped)
@@ -27241,6 +27255,7 @@ var ENDPOINTS = {
27241
27255
  // Clips (org-scoped, served by clip-service)
27242
27256
  CLIPS: (orgId) => `${CLIP_BASE}/orgs/${orgId}/clips`,
27243
27257
  CLIP: (orgId, clipId) => `${CLIP_BASE}/orgs/${orgId}/clips/${clipId}`,
27258
+ CLIPS_BULK_METADATA: (orgId) => `${CLIP_BASE}/orgs/${orgId}/clips/bulk-metadata`,
27244
27259
  CLIP_AGENTS: (orgId, clipId) => `${CLIP_BASE}/orgs/${orgId}/clips/${clipId}/agents`,
27245
27260
  AGENT_CLIPS: (orgId, agentId) => `${CLIP_BASE}/orgs/${orgId}/agents/${agentId}/clips`,
27246
27261
  AGENT_CLIP: (orgId, agentId, clipId) => `${CLIP_BASE}/orgs/${orgId}/agents/${agentId}/clips/${clipId}`,
@@ -27253,6 +27268,9 @@ var ENDPOINTS = {
27253
27268
  BROWSER_PROFILE_RESET: (orgId, profileId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}/reset`,
27254
27269
  BROWSER_PROFILE_CONSENTS: (orgId, profileId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}/consents`,
27255
27270
  BROWSER_PROFILE_CONSENT: (orgId, profileId, clipId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}/consents/${clipId}`,
27271
+ // Live viewer command — on api-server (API_BASE), not clip-service: it rides
27272
+ // the machine control-plane request/reply bridge that lives in api-server.
27273
+ BROWSER_PROFILE_VIEWER_COMMAND: (orgId, profileId) => `${API_BASE}/orgs/${orgId}/browser-profiles/${profileId}/viewer/command`,
27256
27274
  // Clip registry (global, served by clip-service → Pinix Hub proxy)
27257
27275
  CLIP_REGISTRY: () => `${CLIP_BASE}/registry/clips`
27258
27276
  };
@@ -27329,6 +27347,7 @@ var WS_EVENTS = {
27329
27347
  MACHINE_CONFIG_UPDATED: "machine.config.updated",
27330
27348
  MACHINE_CLIP_SYNC: "machine.clip.sync",
27331
27349
  MACHINE_BROWSER_PROFILE_LIFECYCLE: "machine.browser_profile.lifecycle",
27350
+ MACHINE_BROWSER_PROFILE_VIEWER: "machine.browser_profile.viewer",
27332
27351
  AGENT_NEW_SESSION: "agent.new_session",
27333
27352
  CLIP_CREATED: "clip.created",
27334
27353
  CLIP_REMOVED: "clip.removed",
@@ -27359,8 +27378,13 @@ var ParallClient = class _ParallClient {
27359
27378
  /** Proactive refresh when token expires within this window (seconds). */
27360
27379
  static REFRESH_THRESHOLD_S = 5 * 60;
27361
27380
  static normalizeFetchError(err) {
27362
- if (typeof DOMException !== "undefined" && err instanceof DOMException && (err.name === "TimeoutError" || err.name === "AbortError")) {
27363
- return new ApiError(0, "Request timed out", "REQUEST_TIMEOUT");
27381
+ if (typeof DOMException !== "undefined" && err instanceof DOMException) {
27382
+ if (err.name === "TimeoutError") {
27383
+ return new ApiError(0, "Request timed out", "REQUEST_TIMEOUT");
27384
+ }
27385
+ if (err.name === "AbortError") {
27386
+ return new ApiError(0, "Request aborted", "REQUEST_ABORTED");
27387
+ }
27364
27388
  }
27365
27389
  const apiError = new ApiError(0, "Network request failed", "NETWORK_ERROR");
27366
27390
  if (err instanceof Error && err.message && err.message !== "Failed to fetch") {
@@ -27467,13 +27491,18 @@ var ParallClient = class _ParallClient {
27467
27491
  url += `?${qs}`;
27468
27492
  }
27469
27493
  const headers = this.buildHeaders();
27494
+ const timeoutSignal = AbortSignal.timeout(opts?.timeoutMs ?? 15e3);
27495
+ const signal = opts?.signal ? AbortSignal.any([opts.signal, timeoutSignal]) : timeoutSignal;
27470
27496
  let res;
27471
27497
  try {
27472
27498
  res = await fetch(url, {
27473
27499
  method,
27474
27500
  headers,
27475
27501
  body: body ? JSON.stringify(body) : void 0,
27476
- signal: AbortSignal.timeout(opts?.timeoutMs ?? 15e3)
27502
+ signal,
27503
+ // keepalive lets a request fired during page unload (e.g. the browser
27504
+ // viewer's stream.close on pagehide) outlive the document.
27505
+ keepalive: opts?.keepalive
27477
27506
  });
27478
27507
  } catch (err) {
27479
27508
  throw _ParallClient.normalizeFetchError(err);
@@ -27644,6 +27673,10 @@ var ParallClient = class _ParallClient {
27644
27673
  const res = await this.request("GET", ENDPOINTS.ORG_MEMBERS(orgId));
27645
27674
  return res.data;
27646
27675
  }
27676
+ async getTeams(orgId) {
27677
+ const res = await this.request("GET", ENDPOINTS.TEAMS(orgId));
27678
+ return res.data;
27679
+ }
27647
27680
  async getOnlineMembers(orgId) {
27648
27681
  const res = await this.request("GET", ENDPOINTS.ORG_MEMBERS_ONLINE(orgId));
27649
27682
  return res.user_ids ?? [];
@@ -27882,6 +27915,10 @@ var ParallClient = class _ParallClient {
27882
27915
  async createAgentApiKey(orgId, agentId) {
27883
27916
  return this.request("POST", ENDPOINTS.AGENT_API_KEYS(orgId, agentId));
27884
27917
  }
27918
+ /** Revokes all of the agent's active API keys and mints a replacement. */
27919
+ async regenerateAgentApiKey(orgId, agentId) {
27920
+ return this.request("POST", ENDPOINTS.AGENT_API_KEY_REGENERATE(orgId, agentId));
27921
+ }
27885
27922
  async revokeAgentApiKey(orgId, agentId, key) {
27886
27923
  return this.request("DELETE", ENDPOINTS.AGENT_API_KEY(orgId, agentId, key));
27887
27924
  }
@@ -28053,6 +28090,17 @@ var ParallClient = class _ParallClient {
28053
28090
  provider_enabled: providerEnabled
28054
28091
  });
28055
28092
  }
28093
+ /**
28094
+ * Replace the machine's capability set (admin). Primary use: healing a
28095
+ * machine that registered without `browser_provider` during the capability
28096
+ * migration's rolling-deploy window. Removing `agent_host` is rejected by the
28097
+ * server while agents are attached (409 AGENTS_STILL_ATTACHED).
28098
+ */
28099
+ async patchMachineCapabilities(orgId, machineId, capabilities) {
28100
+ return this.request("PATCH", ENDPOINTS.MACHINE_CAPABILITIES(orgId, machineId), {
28101
+ capabilities
28102
+ });
28103
+ }
28056
28104
  /** Get machine-level runtime auth state. */
28057
28105
  async getMachineRuntimeAuth(orgId, machineId) {
28058
28106
  return this.request("GET", ENDPOINTS.MACHINE_RUNTIME_AUTH(orgId, machineId));
@@ -28143,6 +28191,22 @@ var ParallClient = class _ParallClient {
28143
28191
  async postBrowseResponse(requestId, response) {
28144
28192
  return this.request("POST", ENDPOINTS.MACHINES_ME_BROWSE_RESPONSE(requestId), response);
28145
28193
  }
28194
+ /**
28195
+ * `POST /machines/me/browser-profiles/viewer-response/{requestId}` — daemon
28196
+ * reply to a `machine.browser_profile.viewer` control command. Wakes the
28197
+ * api-server request/reply bridge (mirrors {@link postBrowseResponse}).
28198
+ */
28199
+ async postBrowserProfileViewerResponse(requestId, response) {
28200
+ return this.request("POST", ENDPOINTS.MACHINES_ME_BROWSER_PROFILE_VIEWER_RESPONSE(requestId), response);
28201
+ }
28202
+ /**
28203
+ * `POST /orgs/{orgId}/browser-profiles/{profileId}/viewer/command` — drive the
28204
+ * hosted browser live viewer (WebRTC signaling + tab nav). Authz: profile
28205
+ * owner or org admin. api-server brokers the command to the host daemon.
28206
+ */
28207
+ async browserViewerCommand(orgId, profileId, req, opts) {
28208
+ return this.request("POST", ENDPOINTS.BROWSER_PROFILE_VIEWER_COMMAND(orgId, profileId), req, void 0, false, opts);
28209
+ }
28146
28210
  async resizeMachine(orgId, machineId, spec) {
28147
28211
  return this.request("PATCH", ENDPOINTS.MACHINE_SPEC(orgId, machineId), spec);
28148
28212
  }
@@ -28421,6 +28485,9 @@ var ParallClient = class _ParallClient {
28421
28485
  async getWikiNodeSections(orgId, wikiId, params) {
28422
28486
  return this.request("GET", ENDPOINTS.WIKI_NODE_SECTIONS(orgId, wikiId), void 0, params);
28423
28487
  }
28488
+ async search(orgId, params, opts) {
28489
+ return this.request("GET", ENDPOINTS.SEARCH(orgId), void 0, params, false, opts);
28490
+ }
28424
28491
  async searchWiki(orgId, wikiId, params) {
28425
28492
  return this.request("GET", ENDPOINTS.WIKI_SEARCH(orgId, wikiId), void 0, params);
28426
28493
  }
@@ -28516,6 +28583,17 @@ var ParallClient = class _ParallClient {
28516
28583
  async deleteWikiPathScope(orgId, wikiId, scopeId) {
28517
28584
  await this.request("DELETE", ENDPOINTS.WIKI_PATH_SCOPE(orgId, wikiId, scopeId));
28518
28585
  }
28586
+ // ---- Wiki Path Restrictions (narrowing ACL — private subtrees) ----
28587
+ async getWikiRestrictions(orgId, wikiId) {
28588
+ const res = await this.request("GET", ENDPOINTS.WIKI_RESTRICTIONS(orgId, wikiId));
28589
+ return res.data;
28590
+ }
28591
+ async createWikiRestriction(orgId, wikiId, data) {
28592
+ return this.request("POST", ENDPOINTS.WIKI_RESTRICTIONS(orgId, wikiId), data);
28593
+ }
28594
+ async deleteWikiRestriction(orgId, wikiId, restrictionId) {
28595
+ await this.request("DELETE", ENDPOINTS.WIKI_RESTRICTION(orgId, wikiId, restrictionId));
28596
+ }
28519
28597
  async getWikiAccessStatus(orgId, wikiId, path7) {
28520
28598
  return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0, path7 ? { path: path7 } : void 0);
28521
28599
  }
@@ -28678,6 +28756,10 @@ var ParallClient = class _ParallClient {
28678
28756
  async updateClip(orgId, clipId, req) {
28679
28757
  return this.request("PATCH", ENDPOINTS.CLIP(orgId, clipId), req);
28680
28758
  }
28759
+ /** Atomically set display_name and/or description on multiple clip instances (application metadata). */
28760
+ async bulkUpdateClipMetadata(orgId, req) {
28761
+ return this.request("POST", ENDPOINTS.CLIPS_BULK_METADATA(orgId), req);
28762
+ }
28681
28763
  async deleteClip(orgId, clipId) {
28682
28764
  await this.request("DELETE", ENDPOINTS.CLIP(orgId, clipId));
28683
28765
  }
@@ -30209,7 +30291,7 @@ var ParallAgentGateway = class {
30209
30291
  this.dispatchState.mainDispatching = true;
30210
30292
  void this.drainMainBuffer();
30211
30293
  }
30212
- }, 5e3);
30294
+ }, 5e3).unref();
30213
30295
  }
30214
30296
  }
30215
30297
  }
@@ -32407,6 +32489,26 @@ async function waitForOpenClawSessionId(sessionsDir, sessionKey, timeoutMs = 1e4
32407
32489
  }
32408
32490
  function createOpenClawDispatchAdapter(opts) {
32409
32491
  const activeStreams = /* @__PURE__ */ new Map();
32492
+ let configFallbackWarned = false;
32493
+ function resolveDispatchConfig(log) {
32494
+ try {
32495
+ const current = opts.core.config?.current?.();
32496
+ if (current && typeof current === "object") {
32497
+ configFallbackWarned = false;
32498
+ return current;
32499
+ }
32500
+ if (!configFallbackWarned) {
32501
+ configFallbackWarned = true;
32502
+ log?.warn?.(`parall[${opts.accountId}]: config.current() unavailable on this host \u2014 dispatching with the startup config snapshot; model switches need a gateway restart until then`);
32503
+ }
32504
+ } catch (err) {
32505
+ if (!configFallbackWarned) {
32506
+ configFallbackWarned = true;
32507
+ log?.warn?.(`parall[${opts.accountId}]: config.current() failed (${String(err)}) \u2014 dispatching with the startup config snapshot; model switches need a gateway restart until then`);
32508
+ }
32509
+ }
32510
+ return opts.cfg;
32511
+ }
32410
32512
  return {
32411
32513
  abortDispatch(sessionKey) {
32412
32514
  activeStreams.get(sessionKey)?.fail(new Error("dispatch deadline exceeded"));
@@ -32432,7 +32534,7 @@ function createOpenClawDispatchAdapter(opts) {
32432
32534
  let turnGroupKey = "";
32433
32535
  const run = opts.core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
32434
32536
  ctx: buildInboundCtx(opts.core, opts.accountId, event, sessionKey, promptBody, earlierEvents),
32435
- cfg: opts.cfg,
32537
+ cfg: resolveDispatchConfig(context2.log),
32436
32538
  dispatcherOptions: {
32437
32539
  deliver: async (payload) => {
32438
32540
  const replyText = payload.text?.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/parall",
3
- "version": "1.33.0",
3
+ "version": "1.35.0",
4
4
  "description": "OpenClaw channel plugin for Parall IM",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -16,8 +16,8 @@
16
16
  "openclaw.plugin.json"
17
17
  ],
18
18
  "dependencies": {
19
- "@parall/agent-core": "1.33.0",
20
- "@parall/sdk": "1.33.0"
19
+ "@parall/agent-core": "1.35.0",
20
+ "@parall/sdk": "1.35.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "^22.0.0",
@@ -7,129 +7,128 @@ description: "Parall wiki operations: read, search, edit, and propose changes to
7
7
 
8
8
  Manage organization wikis via the Parall CLI. Auth is pre-configured.
9
9
 
10
- ## Browsing (no local state needed)
10
+ ## Mental model
11
+
12
+ Wiki editing works on a **local workspace**: `parall wiki sync` downloads the
13
+ wiki into a directory on disk, you edit those files with your normal file
14
+ tools, then `parall wiki changeset create` uploads the result as a proposal.
15
+ There is no git in the workspace — your edits are detected by diffing against
16
+ the synced baseline.
17
+
18
+ Key facts the commands won't tell you:
19
+
20
+ - **Workspace location is fixed.** Sync output and `parall wiki status` print
21
+ the absolute workspace path (`synced → /path/to/<slug>` / `Mount: ...`).
22
+ Always address wiki files by that absolute path — your shell cwd is usually
23
+ NOT inside the workspace.
24
+ - **Text files only.** Changesets reject binary content. Don't put images or
25
+ archives in the workspace; binary uploads go through the web UI.
26
+ - **`cat`, `search`, `query`, `outline`, and `section` read your local
27
+ workspace copy when it exists** — including your own unproposed edits. Add
28
+ `--remote` to `cat` to read the server version instead.
29
+ - **`search`/`query` are keyword (lexical) matching**, not semantic search.
30
+ `query` additionally ranks whole documents — better for multi-word
31
+ questions; `search` for a single identifier.
32
+ - The `<wiki>` argument is the slug or ID from `parall wiki list`; with a
33
+ single wiki in the org it can be omitted.
34
+
35
+ ## Core workflow
11
36
 
12
37
  ```bash
13
- parall wiki list # List all wikis
14
- parall wiki tree <slug> # List files and directories
15
- parall wiki tree <slug> --path docs/ # List a subdirectory
38
+ parall wiki sync # 1. get/update files (prints workspace path)
39
+ # 2. read + edit files under the workspace path with standard file tools
40
+ parall wiki diff <wiki> # 3. review exactly what you'll propose
41
+ parall wiki changeset create <wiki> --title "..." # 4. submit
16
42
  ```
17
43
 
18
- ## Editing
44
+ Always sync before starting and always check `diff` before proposing — the
45
+ changeset uploads the full content of every changed file.
19
46
 
20
- Wiki editing works on a **local workspace** — a directory on disk where wiki
21
- files are synced. You sync from the server, edit files locally, then propose
22
- a changeset.
47
+ Unprotected paths auto-merge immediately (`auto_merged: true`); protected
48
+ paths stay open for human review. Follow the returned `next_action` either way.
23
49
 
24
- ### Step 1: Sync
50
+ ## Stale base (server moved since your sync)
25
51
 
26
- ```bash
27
- parall wiki sync <slug>
28
- ```
29
-
30
- This downloads wiki files to a local directory. The output includes the
31
- **absolute mount path** for each wiki (e.g. `synced → /path/to/workspace/kb`).
32
-
33
- ### Step 2: Find the mount path
34
-
35
- The sync output JSON contains `synced[].path` — the absolute path where files
36
- live. You can also check it anytime with:
52
+ If files changed on the server after your last sync, `changeset create` is
53
+ rejected both by a CLI precheck and by the server (409 `STALE_BASE`) — so
54
+ you can't silently overwrite someone's concurrent edit. Recovery:
37
55
 
38
56
  ```bash
39
- parall wiki status <slug>
57
+ parall wiki sync # pull latest; your local edits are preserved
58
+ # if a file conflicts, resolve it (see next section)
59
+ parall wiki changeset create <wiki> --title "..."
40
60
  ```
41
61
 
42
- The output includes `Mount: /absolute/path/to/<slug>`.
43
-
44
- Use this path with `read`, `write`, and `edit` tools. For example, if the mount
45
- is `/data/.openclaw/workspace/kb`, then `README.md` is at
46
- `/data/.openclaw/workspace/kb/README.md`.
62
+ ## Sync conflicts
47
63
 
48
- ### Step 3: Edit files
64
+ `sync` three-way merges. When both you and the server changed the same file,
65
+ your file is left intact and the upstream copy lands under
66
+ `<workspace>/.parall-wiki/conflicts/`:
49
67
 
50
- Use standard file tools (`read`, `write`, `edit`) on files under the mount path.
68
+ | Marker | Meaning |
69
+ |--------|---------|
70
+ | `conflicts/<path>.remote` | Server has different content for `<path>` |
71
+ | `conflicts/<path>.remote-deleted` | Server deleted `<path>`; you still have edits |
51
72
 
52
- ### Step 4: Review changes
73
+ All paths below are relative to the workspace root. Pick one:
53
74
 
54
75
  ```bash
55
- parall wiki diff <slug> # Shows unified diff of all local changes
56
- parall wiki status <slug> # Shows which files changed with +/- line counts
57
- ```
76
+ # Accept upstream (drop your edit):
77
+ cp <workspace>/.parall-wiki/conflicts/<path>.remote <workspace>/<path>
58
78
 
59
- Always review before proposing.
79
+ # Keep yours / hand-merge: edit <workspace>/<path> to final content, then
80
+ parall wiki changeset create <wiki> --title "Reconcile <path>"
60
81
 
61
- ### Step 5: Propose changeset
62
-
63
- ```bash
64
- parall wiki changeset create <slug> --title "Description of changes"
82
+ # Accept server delete (.remote-deleted only):
83
+ rm <workspace>/<path>
65
84
  ```
66
85
 
67
- This uploads your local changes. Unprotected paths auto-merge immediately; the
68
- CLI prints `auto_merged: true` and refreshes the local manifest. Protected
69
- paths remain as a changeset for review; follow the returned `next_action`.
70
-
71
- ## Changeset Management
72
-
73
- ```bash
74
- parall wiki changeset list <slug> # List all changesets
75
- parall wiki changeset show <changesetId> <slug> # Show detail + feedback
76
- parall wiki changeset diff <changesetId> <slug> # Show changeset diff
77
- ```
86
+ Then re-run `parall wiki sync` and delete the used marker file. Conflicts
87
+ exit 0 (they need your decision); `failed[]` entries (download error,
88
+ shape-conflict) exit 1 and retry on the next sync.
78
89
 
79
- If a changeset is rejected, fix the files locally and re-propose:
90
+ ## Changesets
80
91
 
81
92
  ```bash
82
- parall wiki changeset create <slug> --update <changesetId>
93
+ parall wiki changeset list <wiki>
94
+ parall wiki changeset show <changesetId> <wiki> # status + feedback
95
+ parall wiki changeset diff <changesetId> <wiki>
96
+ parall wiki changeset create <wiki> --update <id> # re-propose after rejection (title inherited)
83
97
  ```
84
98
 
85
- The title is inherited from the original changeset no need to repeat it.
86
-
87
- ## Handling sync conflicts
88
-
89
- `wiki sync` runs a three-way merge. When both you and the server changed the
90
- same file, sync **does not overwrite your work**. It leaves your file intact
91
- and drops the upstream version under `.parall-wiki/conflicts/`:
92
-
93
- | Marker | Meaning |
94
- |--------|---------|
95
- | `.parall-wiki/conflicts/<path>.remote` | Server has different content (concurrent edit, new file collision, or server changed a file you deleted) |
96
- | `.parall-wiki/conflicts/<path>.remote-deleted` | Server deleted the file; you still have edits |
97
-
98
- stderr prints one line per conflict. Recovery:
99
+ Rejected: read the feedback (`show` / `status`), fix the files, re-propose
100
+ with `--update <id>`. Conflict status: `sync`, resolve, then `--update <id>`.
99
101
 
100
- **Accept upstream** (drop your edit):
101
- ```bash
102
- cp .parall-wiki/conflicts/<path>.remote <path>
103
- parall wiki sync
104
- ```
102
+ Re-propose REPLACES the changeset's previous contents with your current
103
+ workspace diff — to withdraw a file from the proposal, revert it locally
104
+ (restore the synced content) and re-propose; it drops out of the changeset.
105
105
 
106
- **Keep yours** (or hand-merge, then propose):
107
- ```bash
108
- # edit <path> to final content
109
- parall wiki diff <slug>
110
- parall wiki changeset create <slug> --title "Reconcile <path>"
111
- parall wiki sync # fast-forwards after server merges
112
- ```
106
+ ## Discovery & history
113
107
 
114
- **Accept server delete** (`.remote-deleted` only):
115
108
  ```bash
116
- rm <path>
117
- parall wiki sync
109
+ parall wiki query "how is auth configured" <wiki> # multi-word lookup (query FIRST, wiki second)
110
+ parall wiki search "JWT" <wiki> # single keyword (query FIRST, wiki second)
111
+ parall wiki outline <wiki> --path docs/ # heading structure
112
+ parall wiki cat docs/auth.md <wiki> # print a file (--remote for server version)
113
+ parall wiki tree <wiki> # list files
114
+ parall wiki log <wiki> # recent operations
115
+ parall wiki log <wiki> docs/auth.md # per-file commit history
118
116
  ```
119
117
 
120
- Sync failures (`download`, `shape-conflict`) set exit code 1 and retry on
121
- next sync. Conflicts exit 0 — they need your decision, not a retry.
118
+ ## Permissions
122
119
 
123
- ## Discarding local changes
124
-
125
- ```bash
126
- parall wiki reset <slug> # Restore all files to last synced state
127
- ```
120
+ Reads and writes are ACL-checked server-side per path. `parall wiki access
121
+ <path>` shows your level for a path. On a `PERMISSION_DENIED`, errors include
122
+ a `Request approval:` hint — use `parall wiki request-access <path> --reason
123
+ "..."` to file an approval card for a maintainer, then re-sync after approval.
128
124
 
129
- ## History
125
+ ## Recovery
130
126
 
131
127
  ```bash
132
- parall wiki log <slug> # Recent wiki operations
128
+ parall wiki reset <wiki> # discard ALL local edits, restore last-synced state
129
+ parall wiki status <wiki> # local changes + your changesets, anytime
133
130
  ```
134
131
 
135
- CLI success output is JSON; errors may add a plain-text `Request approval:` line on a `PERMISSION_DENIED` — read both. Run `parall wiki --help` for full options.
132
+ CLI success output is JSON on stdout (human summary on stderr); errors state
133
+ the reason and the next step — follow them. `parall wiki --help` for the
134
+ full command list.
package/src/gateway.ts CHANGED
@@ -195,7 +195,7 @@ async function waitForOpenClawSessionId(
195
195
  return null;
196
196
  }
197
197
 
198
- function createOpenClawDispatchAdapter(opts: {
198
+ export function createOpenClawDispatchAdapter(opts: {
199
199
  core: PluginRuntime;
200
200
  cfg: Record<string, unknown>;
201
201
  accountId: string;
@@ -203,6 +203,43 @@ function createOpenClawDispatchAdapter(opts: {
203
203
  workspaceDir: string;
204
204
  }): DispatchAdapter {
205
205
  const activeStreams = new Map<string, ReturnType<typeof createRuntimeEventStream>>();
206
+ let configFallbackWarned = false;
207
+
208
+ /**
209
+ * Resolve the config for one dispatch. OpenClaw hot-reloads
210
+ * `agents.defaults.model` / `models.providers` WITHOUT restarting channel
211
+ * plugins, so the `ctx.cfg` snapshot captured at startAccount goes stale the
212
+ * moment the operator switches the agent's model — every run would keep the
213
+ * old model until the whole gateway restarts. `core.config.current()` is the
214
+ * host's live (hot-reload-refreshed) config; fall back to the startup
215
+ * snapshot on hosts that predate the API. The fallback warns once per
216
+ * degradation streak (latch resets on success) — silently dispatching with
217
+ * the stale snapshot would recreate the invisible model-switch mismatch this
218
+ * function exists to fix.
219
+ */
220
+ function resolveDispatchConfig(log?: { warn?: (msg: string) => void }): Record<string, unknown> {
221
+ try {
222
+ const current = opts.core.config?.current?.();
223
+ if (current && typeof current === 'object') {
224
+ configFallbackWarned = false;
225
+ return current as unknown as Record<string, unknown>;
226
+ }
227
+ if (!configFallbackWarned) {
228
+ configFallbackWarned = true;
229
+ log?.warn?.(
230
+ `parall[${opts.accountId}]: config.current() unavailable on this host — dispatching with the startup config snapshot; model switches need a gateway restart until then`,
231
+ );
232
+ }
233
+ } catch (err) {
234
+ if (!configFallbackWarned) {
235
+ configFallbackWarned = true;
236
+ log?.warn?.(
237
+ `parall[${opts.accountId}]: config.current() failed (${String(err)}) — dispatching with the startup config snapshot; model switches need a gateway restart until then`,
238
+ );
239
+ }
240
+ }
241
+ return opts.cfg;
242
+ }
206
243
 
207
244
  return {
208
245
  abortDispatch(sessionKey: string): void {
@@ -238,7 +275,7 @@ function createOpenClawDispatchAdapter(opts: {
238
275
  promptBody,
239
276
  earlierEvents,
240
277
  ),
241
- cfg: opts.cfg,
278
+ cfg: resolveDispatchConfig(context.log),
242
279
  dispatcherOptions: {
243
280
  deliver: async (payload: { text?: string }) => {
244
281
  const replyText = payload.text?.trim();