@parall/parall 1.52.0 → 1.52.2

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.
@@ -51509,6 +51509,8 @@ var ENDPOINTS = {
51509
51509
  SLACK_HISTORY: (orgId) => `${API_BASE}/orgs/${orgId}/agents/me/slack/history`,
51510
51510
  SLACK_MEMBERS: (orgId) => `${API_BASE}/orgs/${orgId}/agents/me/slack/members`,
51511
51511
  SLACK_STATUS: (orgId) => `${API_BASE}/orgs/${orgId}/agents/me/slack/status`,
51512
+ // WeChat tier-B read verb (agent-only; internal research preview).
51513
+ WECHAT_CONTACTS: (orgId) => `${API_BASE}/orgs/${orgId}/agents/me/wechat/contacts`,
51512
51514
  // Invitations (org-scoped, admin)
51513
51515
  ORG_INVITATIONS: (orgId) => `${API_BASE}/orgs/${orgId}/invitations`,
51514
51516
  ORG_INVITATION: (orgId, invId) => `${API_BASE}/orgs/${orgId}/invitations/${invId}`,
@@ -51670,8 +51672,6 @@ var ENDPOINTS = {
51670
51672
  // Live viewer command — on api-server (API_BASE), not clip-service: it rides
51671
51673
  // the machine control-plane request/reply bridge that lives in api-server.
51672
51674
  BROWSER_PROFILE_VIEWER_COMMAND: (orgId, profileId) => `${API_BASE}/orgs/${orgId}/browser-profiles/${profileId}/viewer/command`,
51673
- // Clip registry (global, served by clip-service → Pinix Hub proxy)
51674
- CLIP_REGISTRY: () => `${CLIP_BASE}/registry/clips`,
51675
51675
  // Edge device endpoints
51676
51676
  ORG_EDGE: (orgId) => `/api/v1/orgs/${orgId}/edge`,
51677
51677
  ORG_EDGE_DEVICES: (orgId) => `/api/v1/orgs/${orgId}/edge/devices`,
@@ -51688,15 +51688,20 @@ var ENDPOINTS = {
51688
51688
  ORG_EDGE_VIEWER_COMMAND: (orgId, edgeId) => `/api/v1/orgs/${orgId}/edge/${edgeId}/viewer/command`,
51689
51689
  CLIP_CONNECTIONS: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/connections`,
51690
51690
  CLIP_CONNECTION: (orgId, connId) => `/api/v1/orgs/${orgId}/clip-connections/${connId}`,
51691
- // Clip registry (v3, org-scoped, served by api-server — `crg_` entries; the
51692
- // Pinix Hub catalog proxy above is a different, id-less surface)
51691
+ // Clip registry (v3, org-scoped, served by api-server — `crg_` entries)
51693
51692
  ORG_CLIP_REGISTRY: (orgId) => `/api/v1/orgs/${orgId}/clip-registry`,
51693
+ ORG_CLIP_REGISTRY_CLIP: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}`,
51694
+ ORG_CLIP_REGISTRY_PUBLISH: (orgId) => `/api/v1/orgs/${orgId}/clip-registry/publish`,
51694
51695
  ORG_CLIP_INSTALL: (orgId) => `/api/v1/orgs/${orgId}/clips/install`,
51695
51696
  ORG_CLIPS_INSTALLED: (orgId) => `/api/v1/orgs/${orgId}/clips/installed`,
51697
+ ORG_CLIP_UNINSTALL: (orgId, clipId) => `/api/v1/orgs/${orgId}/clips/${clipId}/uninstall`,
51698
+ // Per-org agent exec access for one clip (default all agents; PUT human-only)
51699
+ ORG_CLIP_EXEC_ACCESS: (orgId, clipId) => `/api/v1/orgs/${orgId}/clips/${clipId}/exec-access`,
51696
51700
  // MCP clip server config (cap:clip-mcp; publisher-org only — cross-org gets
51697
51701
  // 403 MCP_CROSS_ORG_DISABLED on the whole family, reads included)
51698
51702
  ORG_CLIP_MCP_CONFIG: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config`,
51699
- ORG_CLIP_MCP_TOOLS_REFRESH: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/tools/refresh`
51703
+ ORG_CLIP_MCP_TOOLS_REFRESH: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/tools/refresh`,
51704
+ ORG_CLIP_MCP_OAUTH_DISCONNECT: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/oauth/disconnect`
51700
51705
  };
51701
51706
  var WS_EVENTS = {
51702
51707
  // Client -> Server
@@ -53192,6 +53197,14 @@ var ParallClient = class _ParallClient {
53192
53197
  async setSlackStatus(orgId, input) {
53193
53198
  await this.request("POST", ENDPOINTS.SLACK_STATUS(orgId), input);
53194
53199
  }
53200
+ /**
53201
+ * WeChat tier-B read verb (agent-only; internal research preview): the
53202
+ * account's address book — friend wxids, saved group room ids, followed
53203
+ * official accounts. Same live gate as the send verb.
53204
+ */
53205
+ async listWechatContacts(orgId) {
53206
+ return this.request("GET", ENDPOINTS.WECHAT_CONTACTS(orgId));
53207
+ }
53195
53208
  async listChannelConversations(orgId, connectionId) {
53196
53209
  return this.request("GET", ENDPOINTS.CHANNEL_CONNECTION_CONVERSATIONS(orgId, connectionId));
53197
53210
  }
@@ -53730,17 +53743,11 @@ var ParallClient = class _ParallClient {
53730
53743
  async revokeBrowserProfileConsent(orgId, profileId, clipId) {
53731
53744
  await this.request("DELETE", ENDPOINTS.BROWSER_PROFILE_CONSENT(orgId, profileId, clipId));
53732
53745
  }
53733
- async listRegistryClips(q) {
53734
- const url = ENDPOINTS.CLIP_REGISTRY() + (q ? `?q=${encodeURIComponent(q)}` : "");
53735
- const resp = await this.request("GET", url);
53736
- return resp.data;
53737
- }
53738
53746
  // ---- Clip registry (v3, api-server org registry — `crg_` entries) ----
53739
53747
  /**
53740
53748
  * List registry clips visible to the org: its own plus public+approved
53741
53749
  * cross-org entries. This is the surface `installRegistryClip` and clip
53742
- * connections operate on — NOT the Pinix catalog proxy
53743
- * ({@link listRegistryClips}), whose entries carry no `crg_` id.
53750
+ * connections operate on.
53744
53751
  */
53745
53752
  async listOrgRegistryClips(orgId) {
53746
53753
  const resp = await this.request("GET", `${ENDPOINTS.ORG_CLIP_REGISTRY(orgId)}?limit=100`);
@@ -53755,11 +53762,58 @@ var ParallClient = class _ParallClient {
53755
53762
  async installRegistryClip(orgId, clipId) {
53756
53763
  return this.request("POST", ENDPOINTS.ORG_CLIP_INSTALL(orgId), { clip_id: clipId });
53757
53764
  }
53765
+ /**
53766
+ * Remove an org's install of a registry clip. Keyed by the REGISTRY id
53767
+ * (`crg_`), the same id {@link installRegistryClip} takes — the v2
53768
+ * `deleteClip` operates on a different table and cannot reach these.
53769
+ */
53770
+ async uninstallRegistryClip(orgId, clipId) {
53771
+ await this.request("DELETE", ENDPOINTS.ORG_CLIP_UNINSTALL(orgId, clipId));
53772
+ }
53758
53773
  /** List the org's installed registry clips (full entries). */
53759
53774
  async listInstalledRegistryClips(orgId) {
53760
53775
  const resp = await this.request("GET", ENDPOINTS.ORG_CLIPS_INSTALLED(orgId));
53761
53776
  return resp ?? [];
53762
53777
  }
53778
+ /**
53779
+ * Fetch one registry clip by its `crg_` id. For a clip OWNED by the calling
53780
+ * org the entry additionally carries `review_status`/`review_note` — the
53781
+ * state of the latest submitted version and the reviewer's rejection
53782
+ * feedback. This is the polling surface for "did platform review approve my
53783
+ * public publish yet"; cross-org callers see only public+approved entries
53784
+ * and never any review-state fields.
53785
+ */
53786
+ async getRegistryClip(orgId, clipId) {
53787
+ return this.request("GET", ENDPOINTS.ORG_CLIP_REGISTRY_CLIP(orgId, clipId));
53788
+ }
53789
+ /**
53790
+ * Publish (create, or author-only update) a clip in the org registry,
53791
+ * replacing its whole file set. See {@link PublishRegistryClipRequest} for
53792
+ * the visibility/review semantics; the returned entry carries
53793
+ * `review_status` when a public publish queued a review submission — poll
53794
+ * {@link getRegistryClip} for the outcome.
53795
+ */
53796
+ async publishRegistryClip(orgId, req) {
53797
+ return this.request("POST", ENDPOINTS.ORG_CLIP_REGISTRY_PUBLISH(orgId), req, void 0, false, {
53798
+ timeoutMs: 6e4
53799
+ });
53800
+ }
53801
+ /**
53802
+ * Read a clip's per-org agent exec access. Org-member readable (agents
53803
+ * included, so a denied agent can learn why exec answered
53804
+ * `CLIP_AGENT_NOT_ALLOWED`).
53805
+ */
53806
+ async getClipAgentExecAccess(orgId, clipId) {
53807
+ return this.request("GET", ENDPOINTS.ORG_CLIP_EXEC_ACCESS(orgId, clipId));
53808
+ }
53809
+ /**
53810
+ * Replace a clip's per-org agent exec access. Human-only (agent principals
53811
+ * get 403); under `all_agents` the `agent_ids` must be empty. Every granted
53812
+ * id must be an active agent of this org (else `400 INVALID_INPUT`).
53813
+ */
53814
+ async putClipAgentExecAccess(orgId, clipId, access) {
53815
+ return this.request("PUT", ENDPOINTS.ORG_CLIP_EXEC_ACCESS(orgId, clipId), access);
53816
+ }
53763
53817
  // ---- Edge devices ----
53764
53818
  async listEdgeDevices(orgId) {
53765
53819
  return this.request("GET", ENDPOINTS.ORG_EDGE_DEVICES(orgId));
@@ -54509,10 +54563,16 @@ function buildSendMessageHint(event) {
54509
54563
  const replyTo = event.channelExternalMessageId ? ` --reply-to "${event.channelExternalMessageId}"` : "";
54510
54564
  return `
54511
54565
  <system-reminder>To reply, use the platform verb: \`parall slack send${channelArg}${replyTo} --text <your reply>\`. In channels --reply-to is REQUIRED (the reply lands in that message's thread); in DMs it is optional (DMs are linear). \`parall slack send\` is the ONLY outbound path \u2014 your plain text output is NOT delivered to the external conversation.</system-reminder>`;
54566
+ }
54567
+ if (event.channelProvider === "wechat") {
54568
+ const toArg = event.channelExternalConversationId ? ` --to "${event.channelExternalConversationId}"` : " --to <wxid from this event>";
54569
+ const atArg = event.channelConversationType === "group" ? event.senderId && event.senderId !== "external" ? ` --at "${event.senderId}"` : " --at <wxid of the person you are answering>" : "";
54570
+ return `
54571
+ <system-reminder>To reply, use the platform verb: \`parall wechat send${toArg}${atArg} --text <your reply>\`. In group chats, --at @-mentions the person you are answering. \`parall wechat send\` is the ONLY outbound path \u2014 your plain text output is NOT delivered to the external conversation.</system-reminder>`;
54512
54572
  }
54513
54573
  if (!event.channelProvider) {
54514
54574
  return `
54515
- <system-reminder>To reply, use the channel capability granted in your system prompt \u2014 for Feishu conversations that is \`lark-cli im\`, for Slack it is \`parall slack send\` (pass the message id from this event as --reply-to). That capability is the ONLY outbound path \u2014 your plain text output is NOT delivered to the external conversation.</system-reminder>`;
54575
+ <system-reminder>To reply, use the channel capability granted in your system prompt \u2014 for Feishu conversations that is \`lark-cli im\`, for Slack it is \`parall slack send\` (pass the message id from this event as --reply-to), for WeChat it is \`parall wechat send\`. That capability is the ONLY outbound path \u2014 your plain text output is NOT delivered to the external conversation.</system-reminder>`;
54516
54576
  }
54517
54577
  const convRef = event.channelExternalConversationId ? `chat_id "${event.channelExternalConversationId}"` : "the conversation id named in this event";
54518
54578
  const threadAlt = event.channelExternalMessageId ? ` To reply threaded to this specific message, reference message_id "${event.channelExternalMessageId}".` : "";
@@ -54991,13 +55051,33 @@ var LaneLedger = class {
54991
55051
  };
54992
55052
 
54993
55053
  // ../agent-core/dist/gateway-lane-flow.js
55054
+ function shiftMainBufferGroup(host, groupKey, preserveInjectedFrameBoundary) {
55055
+ const events = [];
55056
+ while (host.dispatchState.mainBuffer[0] && host.dispatchGroupKey(host.dispatchState.mainBuffer[0]) === groupKey && (!preserveInjectedFrameBoundary || events.length === 0)) {
55057
+ events.push(host.dispatchState.mainBuffer.shift());
55058
+ }
55059
+ return events;
55060
+ }
55061
+ function hasUninjectedSameLaneEventAhead(host, event) {
55062
+ const eventIndex = host.dispatchState.mainBuffer.lastIndexOf(event);
55063
+ if (eventIndex <= 0)
55064
+ return false;
55065
+ const groupKey = host.dispatchGroupKey(event);
55066
+ return host.dispatchState.mainBuffer.slice(0, eventIndex).some((buffered) => host.dispatchGroupKey(buffered) === groupKey && !host.injectedMainBufferEvents.has(buffered));
55067
+ }
54994
55068
  async function steerLaneMessage(host, event) {
54995
55069
  const { laneLedger: ledger, opts } = host;
54996
55070
  const adapter = opts.dispatchAdapter;
54997
55071
  if (!ledger || !adapter.enqueueDuringDispatch)
54998
55072
  return;
55073
+ if (hasUninjectedSameLaneEventAhead(host, event))
55074
+ return;
54999
55075
  const folded = await ledger.steerLive(event);
55000
- if (folded && await adapter.enqueueDuringDispatch(opts.runtimeKey, buildEventBody(event), folded.inputLifecycle)) {
55076
+ if (!folded)
55077
+ return;
55078
+ const injected = await adapter.enqueueDuringDispatch(opts.runtimeKey, buildEventBody(event), folded.inputLifecycle);
55079
+ if (injected) {
55080
+ host.injectedMainBufferEvents.add(event);
55001
55081
  opts.log?.info(`steer folded+injected for ${event.messageId} (will drain for bookkeeping)`);
55002
55082
  }
55003
55083
  }
@@ -55629,6 +55709,22 @@ All three forms work \u2014 pick whichever fits:
55629
55709
  Bare URIs and empty-context refs are preferred in most cases \u2014 the platform
55630
55710
  resolves and renders the entity title automatically.
55631
55711
 
55712
+ ### Mentioning people and agents
55713
+
55714
+ A real member mention is a \`prll://usr_...\` reference. Plain \`@Display Name\` is
55715
+ only text: it does not notify a human or trigger an agent.
55716
+
55717
+ When another member must be notified or an agent explicitly triggered, include
55718
+ their user reference in the message body. Prefer the empty-context form because
55719
+ the platform resolves the member's current display name:
55720
+
55721
+ [](prll://usr_xxx)
55722
+
55723
+ Use \`[Display Name](prll://usr_xxx)\` when the surrounding sentence needs an
55724
+ explicit label. Find the user ID in the incoming message or with
55725
+ \`parall members list\`. Never substitute plain \`@Display Name\` when notification
55726
+ or agent dispatch matters.
55727
+
55632
55728
  ### URI format
55633
55729
 
55634
55730
  \`prll://\` follows standard URI structure: \`scheme://authority/path?query#fragment\`.
@@ -55858,9 +55954,12 @@ import * as path2 from "node:path";
55858
55954
  import { fileURLToPath } from "node:url";
55859
55955
  var CAPABILITY_FEISHU_CLI = "feishu-cli";
55860
55956
  var CAPABILITY_SLACK_SEND = "slack-send";
55957
+ var CAPABILITY_WECHAT_SEND = "wechat-send";
55861
55958
  function channelCapabilityKeyFor(provider) {
55862
55959
  if (provider === "slack")
55863
55960
  return CAPABILITY_SLACK_SEND;
55961
+ if (provider === "wechat")
55962
+ return CAPABILITY_WECHAT_SEND;
55864
55963
  return `${provider}-cli`;
55865
55964
  }
55866
55965
  var CHANNEL_POINTER_MAGIC = "parall channel capability pointer";
@@ -57043,6 +57142,7 @@ var ParallAgentGateway = class {
57043
57142
  // (stable mapping; avoids one connection fetch per inbound message).
57044
57143
  channelConnectionProviders = /* @__PURE__ */ new Map();
57045
57144
  dispatchedMessages = /* @__PURE__ */ new Set();
57145
+ injectedMainBufferEvents = /* @__PURE__ */ new WeakSet();
57046
57146
  // Per-WorkItem failure backoff for typed dispatch consumption — see
57047
57147
  // LaneFlowHost.typedRedriveBackoff in gateway-lane-flow.ts.
57048
57148
  typedRedriveBackoff = /* @__PURE__ */ new Map();
@@ -58255,13 +58355,10 @@ var ParallAgentGateway = class {
58255
58355
  break;
58256
58356
  }
58257
58357
  const groupKey = this.dispatchGroupKey(this.dispatchState.mainBuffer[0]);
58258
- const events = [];
58259
- while (this.dispatchState.mainBuffer[0] && this.dispatchGroupKey(this.dispatchState.mainBuffer[0]) === groupKey) {
58260
- events.push(this.dispatchState.mainBuffer.shift());
58261
- }
58358
+ const hasPendingInjections = this.opts.dispatchAdapter.hasPendingInjections?.(this.opts.runtimeKey) ?? false;
58359
+ const events = shiftMainBufferGroup(this.laneFlowHost(), groupKey, hasPendingInjections && this.opts.dispatchAdapter.inputLifecycleMode === "explicit");
58262
58360
  const event = events[events.length - 1];
58263
58361
  const earlier = events.slice(0, -1);
58264
- const hasPendingInjections = this.opts.dispatchAdapter.hasPendingInjections?.(this.opts.runtimeKey) ?? false;
58265
58362
  const pendingFork = hasPendingInjections ? [] : this.dispatchState.pendingForkResults.splice(0);
58266
58363
  const forkPrefix = buildForkResultPrefix(pendingFork);
58267
58364
  this.dispatchState.mainCurrentTargetId = event.targetId;
@@ -58974,7 +59071,7 @@ var ParallAgentGateway = class {
58974
59071
  }
58975
59072
  }
58976
59073
  const keys = this.opts.getCapabilityKeys?.() ?? [];
58977
- const cliCapable = provider ? keys.includes(channelCapabilityKeyFor(provider)) : keys.some((k) => k.endsWith("-cli") || k === CAPABILITY_SLACK_SEND);
59074
+ const cliCapable = provider ? keys.includes(channelCapabilityKeyFor(provider)) : keys.some((k) => k.endsWith("-cli") || k === CAPABILITY_SLACK_SEND || k === CAPABILITY_WECHAT_SEND);
58978
59075
  const event = {
58979
59076
  type: "channel_message",
58980
59077
  targetId: conv.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/parall",
3
- "version": "1.52.0",
3
+ "version": "1.52.2",
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.52.0",
20
- "@parall/sdk": "1.52.0"
19
+ "@parall/agent-core": "1.52.2",
20
+ "@parall/sdk": "1.52.2"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "^22.0.0",
@@ -1,46 +1,60 @@
1
1
  ---
2
2
  name: parall-clips
3
- description: "Parall clip operations: list installed clips, invoke clip commands, inspect clip details. Use when: the task requires external capabilities (GitHub, web search, etc.), user asks about available tools/clips, or you need to call a clip command."
3
+ description: "Parall clip operations: list installed clips and their connections, inspect clip commands/tools, execute clip commands on an explicit connection. Use when: the task requires external capabilities (GitHub, web search, etc.), user asks about available tools/clips, or you need to call a clip command."
4
4
  ---
5
5
 
6
6
  # Parall Clips
7
7
 
8
8
  Clips are packaged capabilities that let agents operate external systems —
9
- APIs and websites — through named commands installed in the org.
9
+ APIs, websites, remote tools — through named commands installed in the org.
10
10
 
11
11
  ## Discover
12
12
 
13
13
  ```bash
14
- parall clip list # clips installed in this org
15
- parall clip info <alias> # commands, params, version
14
+ parall clip list # installed clips + each clip's connections
15
+ parall clip info <clip> # commands + per-command params (manifest)
16
+ parall clip connections <clip> # one clip's connections, full rows
17
+ parall clip tools <clip> # MCP clips only: live tool schemas
16
18
  ```
17
19
 
18
- ## Invoke
20
+ `clip list` answers both discovery questions at once: WHICH clip (name,
21
+ description, version) and WHERE it can run — every connection with its
22
+ `ccn_…` id, alias, and target kind:
19
23
 
20
- ```bash
21
- parall clip invoke <alias> <command> [input] [--timeout <ms>] # timeout default 30s
22
- # input: JSON string or plain text, per the command's params in `info`
23
- parall clip invoke github-tools list-repos '{"org":"acme"}'
24
- ```
25
-
26
- Results are JSON on stdout; failures print an error.
24
+ - `cloud` — an org-shared cloud profile (a maintainer's signed-in browser)
25
+ - `desktop` a member's own device (only its owner can exec through it)
26
+ - `mcp` a remote MCP tool server
27
+ - `device` a device whose placement could not be resolved just now (the
28
+ device list was unavailable); don't guess which kind it is — re-run
29
+ discovery, and treat a persistent `device` like an unverified target
30
+ - `orphaned` the target device is gone; the connection is unusable
27
31
 
28
- ## Execute on an Edge device (registry clips)
32
+ Pick the connection whose alias names the account/device the task needs
33
+ (e.g. `ins-nyc` vs `ins-boston`). A row without an alias can only be
34
+ referenced by `ccn_` id — when aliases are missing and several connections
35
+ could match, ask a human to name them in the Clip Console rather than
36
+ guessing which signed-in account you are about to act through.
29
37
 
30
- Registry clips run on an Edge — a member's desktop, or an org-shared cloud
31
- profile. **Name the target explicitly.** A cloud profile has NO implicit
32
- route; omitting the target entirely is a desktop-only legacy form that
33
- reaches just YOUR OWN online desktop device — never a shared cloud profile.
38
+ ## Execute
34
39
 
35
40
  ```bash
36
- parall clip exec <clip> <command> [args] --connection <id|alias> # the normal form
41
+ parall clip exec <clip> <command> [args] --connection <ccn_|alias> # the normal form
37
42
  parall clip exec browser-tools screenshot '{"url":"…"}' --connection cloud-main
38
43
  ```
39
44
 
45
+ Build `args` as JSON per the command's params from `clip info` (or the
46
+ tool's `inputSchema` from `clip tools` for MCP clips — tool names are NOT
47
+ frozen in the manifest, so never guess a tool name or its argument shape).
48
+ Results are JSON on stdout; failures print a typed error.
49
+
50
+ **Name the target explicitly.** A cloud profile has NO implicit route;
51
+ omitting the target entirely is a desktop-only legacy form that reaches just
52
+ YOUR OWN online desktop device — never a shared cloud profile.
53
+
40
54
  - A cloud (hosted) profile is reachable ONLY via `--connection` — the clip
41
- connection its maintainer bound (`ccn_…` id or alias). That binding IS your
42
- authorization; without one the server answers `HOSTED_CONNECTION_REQUIRED`
43
- and the fix is to ask an owner/admin to bind the clip, never to retry.
55
+ connection its maintainer bound. That binding IS your authorization;
56
+ without one the server answers `HOSTED_CONNECTION_REQUIRED` and the fix
57
+ is to ask an owner/admin to bind the clip, never to retry.
44
58
  - `--edge <edgeId>` targets only a desktop device YOU own.
45
59
  - Waiting on a cloud profile is handled by the CLI: `EDGE_ACTIVATING` (cold
46
60
  start), `EDGE_BUSY` (another exec is running) and
@@ -52,19 +66,10 @@ parall clip exec browser-tools screenshot '{"url":"…"}' --connection cloud-mai
52
66
 
53
67
  ## MCP clips (remote tool servers)
54
68
 
55
- Some registry clips are backed by a remote MCP server instead of an Edge
56
- device. The command is an MCP tool name and the args are that tool's JSON
57
- arguments — but **MCP tool names are NOT frozen in `clip info`, so discover
58
- them first; never guess a tool name or its argument shape**. Before invoking,
59
- find the connection AND the tool schemas:
60
-
61
- ```bash
62
- parall clip connections <alias> # the ccn_ id / alias to pass to --connection
63
- parall clip tools <alias> # tool names + descriptions + inputSchema (JSON)
64
- ```
65
-
66
- Read each tool's `inputSchema` from `clip tools` to build valid args, then
67
- exec against that explicit target — same form as an Edge clip:
69
+ A connection with target `mcp` routes to a remote MCP server; the command
70
+ is an MCP tool name and the args are that tool's JSON arguments. Read each
71
+ tool's `inputSchema` from `clip tools` first, then exec against the
72
+ explicit target the same form as any other clip:
68
73
 
69
74
  ```bash
70
75
  parall clip exec <clip> <tool> [json-args] --connection <ccn_|alias>
@@ -82,13 +87,11 @@ parall clip exec <clip> <tool> [json-args] --connection <ccn_|alias>
82
87
 
83
88
  ## Behavior rules
84
89
 
85
- - An authorization error (clip not bound to you) is a fail-fast: ask the
86
- clip's owner or an admin to bind it do not retry or work around it.
87
- - If the executing runtime is offline or the call times out, report that
90
+ - An authorization error (`HOSTED_CONNECTION_REQUIRED`, `FORBIDDEN`) is a
91
+ fail-fast: ask the clip's maintainer or an org admin to bind the clip or
92
+ grant the connection do not retry or work around it.
93
+ - If the target device is offline or the call times out, report that
88
94
  plainly; do not queue, and never fabricate a result for a run that errored.
89
- - Hosted browser activation is handled by the CLI: it waits (bounded) while a
90
- cold hosted browser starts, so if the invoke still fails, report the error —
91
- do not blind-retry in a loop.
92
95
  - **`OUTCOME_UNKNOWN` is never retryable.** It means the command was
93
96
  dispatched and MAY HAVE EXECUTED even though no result came back. Retrying
94
97
  could post, order or delete twice. Verify the effect through the system you
@@ -104,5 +107,7 @@ parall clip exec <clip> <tool> [json-args] --connection <ccn_|alias>
104
107
  - A clip may act through a person's real logged-in account — outward,
105
108
  irreversible, or spending actions (post, order, delete, pay) get the same
106
109
  caution as any shared-state change: confirm when intent isn't explicit.
110
+ The connection's alias/target tells you WHICH account you are acting as —
111
+ if that is ambiguous, resolve it with a human before acting, not after.
107
112
  - Reach for `parall clip list` whenever a task needs capabilities beyond
108
113
  built-in tools.