@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.
- package/dist/index.bundle.mjs +117 -20
- package/package.json +3 -3
- package/skills/parall-clips/SKILL.md +45 -40
package/dist/index.bundle.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
58259
|
-
|
|
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.
|
|
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.
|
|
20
|
-
"@parall/sdk": "1.52.
|
|
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,
|
|
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
|
|
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
|
|
15
|
-
parall clip info <
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 <
|
|
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
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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 (
|
|
86
|
-
clip's
|
|
87
|
-
|
|
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.
|