@parall/parall 1.36.1 → 1.37.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/hooks.js +2 -2
- package/dist/index.bundle.mjs +276 -8
- package/package.json +3 -3
- package/skills/parall-platform/SKILL.md +27 -6
- package/src/hooks.ts +2 -2
package/dist/hooks.js
CHANGED
|
@@ -72,9 +72,9 @@ To respond, you **must** use the Parall CLI via the exec (Bash) tool. If \`paral
|
|
|
72
72
|
|
|
73
73
|
### Event types and where to reply
|
|
74
74
|
|
|
75
|
-
- **\`[Event: message.new]\`** — includes \`[Chat: ... (prll://cht_xxx)]\`. Reply into that chat:
|
|
75
|
+
- **\`[Event: message.new]\`** — includes \`[Chat: ... (prll://cht_xxx)]\`. Reply into that chat. Write the reply with your file tool first (shell-safe), then send it — don't wrap message content in \`--text "..."\`, since the shell expands \`$1,000\`→\`,000\` and runs \`$(...)\` inside double quotes:
|
|
76
76
|
|
|
77
|
-
parall messages send prll://cht_xxx --text
|
|
77
|
+
parall messages send prll://cht_xxx --text-file /tmp/reply.md
|
|
78
78
|
|
|
79
79
|
- **\`[Event: task.assigned]\` / \`[Event: task.comment.created]\`** — includes \`[Task: ... (prll://tsk_xxx)]\`. Act on the task; use task CLI subcommands (\`tasks update\`, \`tasks comment\`). See the \`parall-tasks\` skill.
|
|
80
80
|
|
package/dist/index.bundle.mjs
CHANGED
|
@@ -26662,6 +26662,17 @@ function buildEventBody(event) {
|
|
|
26662
26662
|
if (event.attachedUri)
|
|
26663
26663
|
lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
|
|
26664
26664
|
lines.push("", event.body);
|
|
26665
|
+
} else if (event.type === "channel_message") {
|
|
26666
|
+
lines.push(`[Event: channel.message]`);
|
|
26667
|
+
const providerLabel = sanitizeMeta(event.channelProvider ?? "external IM");
|
|
26668
|
+
const convLabel = event.channelExternalConversationId ? `${sanitizeMeta(event.channelExternalConversationId)} (${sanitizeMeta(event.channelConversationType ?? "conversation")})` : sanitizeMeta(event.channelConversationType ?? "conversation");
|
|
26669
|
+
lines.push(`[Channel: ${providerLabel} | conversation: ${convLabel}]`);
|
|
26670
|
+
lines.push(`[From: ${sanitizeMeta(event.senderName)} (external user, not a Parall member)]`);
|
|
26671
|
+
if (event.channelExternalMessageId) {
|
|
26672
|
+
lines.push(`[External message ID: ${sanitizeMeta(event.channelExternalMessageId)}]`);
|
|
26673
|
+
}
|
|
26674
|
+
lines.push(`[Audience: this conversation lives on ${providerLabel}, OUTSIDE Parall. Readers cannot open prll:// links, Parall cards, or internal attachments \u2014 never include them in replies. Write plain conversational text.]`);
|
|
26675
|
+
lines.push("", event.body);
|
|
26665
26676
|
} else if (event.type === "external_trigger") {
|
|
26666
26677
|
lines.push(`[Event: external.trigger]`);
|
|
26667
26678
|
lines.push(`[Trigger: prll://${event.targetId}]`);
|
|
@@ -26703,7 +26714,7 @@ function buildSendMessageHint(event) {
|
|
|
26703
26714
|
}
|
|
26704
26715
|
if (event.targetId.startsWith("cht_")) {
|
|
26705
26716
|
return `
|
|
26706
|
-
<system-reminder>To reply, run
|
|
26717
|
+
<system-reminder>To reply, run \`parall messages send prll://${event.targetId} --text-file - <<'EOF'\` \u2026 \`EOF\` \u2014 the quoted heredoc keeps \`$\`, backticks and apostrophes literal (plain \`--text "$1,000"\` sends \`,000\`). Your plain text output is not delivered to the chat.</system-reminder>`;
|
|
26707
26718
|
}
|
|
26708
26719
|
if (event.targetId.startsWith("tsk_")) {
|
|
26709
26720
|
return `
|
|
@@ -26712,6 +26723,14 @@ function buildSendMessageHint(event) {
|
|
|
26712
26723
|
if (event.targetId.startsWith("sch_")) {
|
|
26713
26724
|
return `
|
|
26714
26725
|
<system-reminder>To communicate, use the CLI: \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
|
|
26726
|
+
}
|
|
26727
|
+
if (event.type === "channel_message") {
|
|
26728
|
+
const clipLabel = event.channelProvider ? `the \`${event.channelProvider}\` clip's` : "your channel provider clip's";
|
|
26729
|
+
const apiLabel = event.channelProvider ?? "external platform";
|
|
26730
|
+
const target = event.channelExternalConversationId ? `{"chat_id": "${event.channelExternalConversationId}", "text": "..."}` : `{"chat_id": "<conversation id>", "text": "..."}`;
|
|
26731
|
+
const threadAlt = event.channelExternalMessageId ? ` To reply in-thread to this specific message, use {"message_id": "${event.channelExternalMessageId}", "text": "..."} instead.` : "";
|
|
26732
|
+
return `
|
|
26733
|
+
<system-reminder>To reply, invoke ${clipLabel} \`send_message\` command with ${target} \u2014 your plain text output is NOT delivered to the external conversation.${threadAlt} The same clip's \`call\` command reaches the wider ${apiLabel} API when needed.</system-reminder>`;
|
|
26715
26734
|
}
|
|
26716
26735
|
if (event.type === "external_trigger" || event.targetId.startsWith("xtr_")) {
|
|
26717
26736
|
return `
|
|
@@ -26919,6 +26938,8 @@ Or upload first and reuse across chats:
|
|
|
26919
26938
|
parall messages send prll://cht_aaa --attachment att_yyy --text "Report"
|
|
26920
26939
|
parall messages send prll://cht_bbb --attachment att_yyy --text "FYI"
|
|
26921
26940
|
|
|
26941
|
+
The \`--text\` captions above are safe short literals. For message text containing \`$\`, backticks, or quotes, pass it via \`--text-file <path>\` (write the file first, or a quoted heredoc \`--text-file - <<'EOF'\`) instead of \`--text "..."\` \u2014 inside double quotes the shell turns \`$1,000\` into \`,000\` and executes \`$(...)\`.
|
|
26942
|
+
|
|
26922
26943
|
### When to reference
|
|
26923
26944
|
|
|
26924
26945
|
- **Origin** \u2014 always link the message or task that triggered your work
|
|
@@ -27033,6 +27054,7 @@ var ENDPOINTS = {
|
|
|
27033
27054
|
// Org-scoped
|
|
27034
27055
|
ORG: (orgId) => `${API_BASE}/orgs/${orgId}`,
|
|
27035
27056
|
ORG_MEMBERS: (orgId) => `${API_BASE}/orgs/${orgId}/members`,
|
|
27057
|
+
ORG_MEMBERS_FORMER: (orgId) => `${API_BASE}/orgs/${orgId}/members/former`,
|
|
27036
27058
|
TEAMS: (orgId) => `${API_BASE}/orgs/${orgId}/teams`,
|
|
27037
27059
|
ORG_MEMBERS_ONLINE: (orgId) => `${API_BASE}/orgs/${orgId}/members/online`,
|
|
27038
27060
|
ORG_MEMBER: (orgId, userId) => `${API_BASE}/orgs/${orgId}/members/${userId}`,
|
|
@@ -27182,6 +27204,19 @@ var ENDPOINTS = {
|
|
|
27182
27204
|
EXTERNAL_TRIGGER: (orgId, triggerId) => `${API_BASE}/orgs/${orgId}/external-triggers/${triggerId}`,
|
|
27183
27205
|
EXTERNAL_TRIGGER_RUNS: (orgId) => `${API_BASE}/orgs/${orgId}/external-trigger-runs`,
|
|
27184
27206
|
EXTERNAL_TRIGGER_RUN: (orgId, runId) => `${API_BASE}/orgs/${orgId}/external-trigger-runs/${runId}`,
|
|
27207
|
+
// External IM channel (org-scoped, platform-mediated Feishu/Slack)
|
|
27208
|
+
CHANNEL_CONNECTIONS: (orgId) => `${API_BASE}/orgs/${orgId}/channel-connections`,
|
|
27209
|
+
CHANNEL_CONNECTION: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}`,
|
|
27210
|
+
CHANNEL_CONNECTION_CREDENTIALS: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/credentials`,
|
|
27211
|
+
CHANNEL_CONNECTION_INGRESS_TOKEN_REGENERATE: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/ingress-token/regenerate`,
|
|
27212
|
+
CHANNEL_CONNECTION_CONVERSATIONS: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/conversations`,
|
|
27213
|
+
CHANNEL_CONVERSATION: (orgId, conversationId) => `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}`,
|
|
27214
|
+
CHANNEL_CONVERSATION_MESSAGES: (orgId, conversationId) => `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}/messages`,
|
|
27215
|
+
CHANNEL_CONVERSATION_SESSION: (orgId, conversationId) => `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}/session`,
|
|
27216
|
+
CHANNEL_MESSAGE: (orgId, messageId) => `${API_BASE}/orgs/${orgId}/channel-messages/${messageId}`,
|
|
27217
|
+
CHANNEL_PROVISIONING: (orgId) => `${API_BASE}/orgs/${orgId}/channel-provisioning`,
|
|
27218
|
+
CHANNEL_PROVISIONING_SESSION: (orgId, sessionId) => `${API_BASE}/orgs/${orgId}/channel-provisioning/${sessionId}`,
|
|
27219
|
+
CHANNEL_PROVISIONING_CANCEL: (orgId, sessionId) => `${API_BASE}/orgs/${orgId}/channel-provisioning/${sessionId}/cancel`,
|
|
27185
27220
|
// Invitations (org-scoped, admin)
|
|
27186
27221
|
ORG_INVITATIONS: (orgId) => `${API_BASE}/orgs/${orgId}/invitations`,
|
|
27187
27222
|
ORG_INVITATION: (orgId, invId) => `${API_BASE}/orgs/${orgId}/invitations/${invId}`,
|
|
@@ -27199,9 +27234,16 @@ var ENDPOINTS = {
|
|
|
27199
27234
|
INVITE_LINK_JOIN: `${API_BASE}/invite-link/join`,
|
|
27200
27235
|
// Wikis (org-scoped, served by wiki-service)
|
|
27201
27236
|
WIKIS: (orgId) => `${WIKI_BASE}/orgs/${orgId}/wikis`,
|
|
27237
|
+
// Recycle bin — soft-deleted wikis (owner only). Must precede WIKI in the
|
|
27238
|
+
// backend router so `deleted` isn't captured as a {wikiId}; the SDK builder
|
|
27239
|
+
// is just a string.
|
|
27240
|
+
WIKIS_DELETED: (orgId) => `${WIKI_BASE}/orgs/${orgId}/wikis/deleted`,
|
|
27241
|
+
// Detail URL — also reused for DELETE (soft-delete) and `${WIKI}/restore`.
|
|
27202
27242
|
WIKI: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}`,
|
|
27243
|
+
WIKI_RESTORE: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/restore`,
|
|
27203
27244
|
WIKI_TREE: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/tree`,
|
|
27204
27245
|
WIKI_BLOB: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/blob`,
|
|
27246
|
+
WIKI_COPY: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/copy`,
|
|
27205
27247
|
WIKI_NODE_SECTIONS: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/node-sections`,
|
|
27206
27248
|
WIKI_SEARCH: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/search`,
|
|
27207
27249
|
WIKI_PAGE_INDEX: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/page-index`,
|
|
@@ -27284,6 +27326,14 @@ var ENDPOINTS = {
|
|
|
27284
27326
|
BILLING_AUTO_RELOAD: (orgId) => `${API_BASE}/orgs/${orgId}/billing/auto-reload`,
|
|
27285
27327
|
BILLING_SETUP_INTENT: (orgId) => `${API_BASE}/orgs/${orgId}/billing/setup-intent`,
|
|
27286
27328
|
COMPUTE_PRICING: () => `${API_BASE}/billing/compute-pricing`,
|
|
27329
|
+
// Runtime capability table (public, no auth) — SSOT for the create/settings
|
|
27330
|
+
// interlock: per-runtime compute modes, native model family, cross-family
|
|
27331
|
+
// availability, and recommended model.
|
|
27332
|
+
RUNTIMES: () => `${API_BASE}/runtimes`,
|
|
27333
|
+
// Model catalog (public, no auth) — served from the server's DB-backed
|
|
27334
|
+
// snapshot. Optional include_model keeps an agent's pinned hidden legacy
|
|
27335
|
+
// model representable in settings pickers.
|
|
27336
|
+
MODELS: (includeModel) => includeModel ? `${API_BASE}/models?include_model=${encodeURIComponent(includeModel)}` : `${API_BASE}/models`,
|
|
27287
27337
|
// ADMIN_GRANTS intentionally NOT exported here — it sits under the
|
|
27288
27338
|
// unauthenticated `/internal/admin/*` surface and must not bleed into the
|
|
27289
27339
|
// public SDK. Admin dashboard hits the URL directly from its own client.
|
|
@@ -27296,6 +27346,7 @@ var ENDPOINTS = {
|
|
|
27296
27346
|
AGENT_CLIP: (orgId, agentId, clipId) => `${CLIP_BASE}/orgs/${orgId}/agents/${agentId}/clips/${clipId}`,
|
|
27297
27347
|
CLIP_INVOKE: (orgId) => `${CLIP_BASE}/orgs/${orgId}/invoke`,
|
|
27298
27348
|
CLIP_ONLINE: (orgId) => `${CLIP_BASE}/orgs/${orgId}/online`,
|
|
27349
|
+
BROWSER_RUNTIME_STATUS: (orgId) => `${CLIP_BASE}/orgs/${orgId}/browser-runtime/status`,
|
|
27299
27350
|
BROWSER_PROFILES: (orgId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles`,
|
|
27300
27351
|
BROWSER_PROFILE: (orgId, profileId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}`,
|
|
27301
27352
|
BROWSER_PROFILE_OPEN: (orgId, profileId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}/open`,
|
|
@@ -27349,10 +27400,13 @@ var WS_EVENTS = {
|
|
|
27349
27400
|
INVITATION_REVOKED: "invitation.revoked",
|
|
27350
27401
|
ORG_JOIN_REQUEST_NEW: "org.join_request.new",
|
|
27351
27402
|
ORG_INVITE_LINK_JOINED: "org.invite_link.joined",
|
|
27403
|
+
ORG_MEMBER_REMOVED: "org.member.removed",
|
|
27352
27404
|
AGENT_CONFIG_UPDATE: "agent_config.update",
|
|
27353
27405
|
PRESENCE_UPDATE: "presence.update",
|
|
27354
27406
|
WIKI_CHANGESET_CREATED: "wiki.changeset.created",
|
|
27355
27407
|
WIKI_CHANGESET_UPDATED: "wiki.changeset.updated",
|
|
27408
|
+
WIKI_DELETED: "wiki.deleted",
|
|
27409
|
+
WIKI_RESTORED: "wiki.restored",
|
|
27356
27410
|
COMMENT_CREATED: "comment.created",
|
|
27357
27411
|
COMMENT_UPDATED: "comment.updated",
|
|
27358
27412
|
COMMENT_DELETED: "comment.deleted",
|
|
@@ -27380,6 +27434,7 @@ var WS_EVENTS = {
|
|
|
27380
27434
|
MACHINE_FILESYSTEM_BROWSE: "machine.filesystem.browse",
|
|
27381
27435
|
MACHINE_UPDATE: "machine.update",
|
|
27382
27436
|
MACHINE_CONFIG_UPDATED: "machine.config.updated",
|
|
27437
|
+
MACHINE_AGENT_CONFIG_UPDATED: "machine.agent_config.updated",
|
|
27383
27438
|
MACHINE_CLIP_SYNC: "machine.clip.sync",
|
|
27384
27439
|
MACHINE_BROWSER_PROFILE_LIFECYCLE: "machine.browser_profile.lifecycle",
|
|
27385
27440
|
MACHINE_BROWSER_PROFILE_VIEWER: "machine.browser_profile.viewer",
|
|
@@ -27719,6 +27774,13 @@ var ParallClient = class _ParallClient {
|
|
|
27719
27774
|
const res = await this.request("GET", ENDPOINTS.ORG_MEMBERS(orgId));
|
|
27720
27775
|
return res.data;
|
|
27721
27776
|
}
|
|
27777
|
+
/** User IDs of soft-removed (former) org members — for marking their avatar /
|
|
27778
|
+
* name as "left" where dormant relations still surface them (DMs, task
|
|
27779
|
+
* assignees, message history). */
|
|
27780
|
+
async getFormerMemberIds(orgId) {
|
|
27781
|
+
const res = await this.request("GET", ENDPOINTS.ORG_MEMBERS_FORMER(orgId));
|
|
27782
|
+
return res.user_ids ?? [];
|
|
27783
|
+
}
|
|
27722
27784
|
async getTeams(orgId) {
|
|
27723
27785
|
const res = await this.request("GET", ENDPOINTS.TEAMS(orgId));
|
|
27724
27786
|
return res.data;
|
|
@@ -28207,20 +28269,25 @@ var ParallClient = class _ParallClient {
|
|
|
28207
28269
|
return res.data;
|
|
28208
28270
|
}
|
|
28209
28271
|
/** `POST /machines/me/browser-profiles/{profileId}/status` — report runtime status. */
|
|
28210
|
-
async reportBrowserProfileStatus(profileId, status, errorMsg) {
|
|
28272
|
+
async reportBrowserProfileStatus(profileId, status, errorMsg, generation) {
|
|
28211
28273
|
await this.request("POST", ENDPOINTS.MACHINES_ME_BROWSER_PROFILE_STATUS(profileId), {
|
|
28212
28274
|
status,
|
|
28213
|
-
...errorMsg ? { error_msg: errorMsg } : {}
|
|
28275
|
+
...errorMsg ? { error_msg: errorMsg } : {},
|
|
28276
|
+
...generation !== void 0 ? { generation } : {}
|
|
28214
28277
|
});
|
|
28215
28278
|
}
|
|
28216
28279
|
/**
|
|
28217
28280
|
* `POST /machines/me/health` — bump the Machine's `updated_at` to now and
|
|
28218
|
-
* report daemon state. The daemon
|
|
28219
|
-
*
|
|
28281
|
+
* report daemon state. The daemon calls this on startup and again whenever
|
|
28282
|
+
* periodic runtime re-detection produces a CHANGED result (there is no
|
|
28283
|
+
* fixed-cadence keepalive loop today — steady state posts nothing). All
|
|
28220
28284
|
* fields are optional and only persisted when changed:
|
|
28221
28285
|
* - `daemonVersion` — the running bundle/launcher version.
|
|
28222
28286
|
* - `selfUpdateCapable` — whether the daemon can act on a machine.update
|
|
28223
28287
|
* signal (true under a service manager, false for bare `npx` foreground).
|
|
28288
|
+
* - `detectedRuntimes` — runtime CLIs found on the host. Omitted = no
|
|
28289
|
+
* report this beat (server keeps the stored value); [] = detection ran
|
|
28290
|
+
* and found nothing (server clears to empty).
|
|
28224
28291
|
*/
|
|
28225
28292
|
async postMachineHeartbeat(opts) {
|
|
28226
28293
|
const body = {};
|
|
@@ -28228,6 +28295,8 @@ var ParallClient = class _ParallClient {
|
|
|
28228
28295
|
body.daemon_version = opts.daemonVersion;
|
|
28229
28296
|
if (opts?.selfUpdateCapable !== void 0)
|
|
28230
28297
|
body.self_update_capable = opts.selfUpdateCapable;
|
|
28298
|
+
if (opts?.detectedRuntimes !== void 0)
|
|
28299
|
+
body.detected_runtimes = opts.detectedRuntimes;
|
|
28231
28300
|
return this.request("POST", ENDPOINTS.MACHINES_ME_HEALTH, Object.keys(body).length > 0 ? body : void 0);
|
|
28232
28301
|
}
|
|
28233
28302
|
async reportAgentWorkspaceState(agentId, state) {
|
|
@@ -28546,6 +28615,59 @@ var ParallClient = class _ParallClient {
|
|
|
28546
28615
|
async deleteExternalConnection(orgId, connectionId) {
|
|
28547
28616
|
return this.request("DELETE", ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId));
|
|
28548
28617
|
}
|
|
28618
|
+
// ---- External IM channel (org-scoped) ----
|
|
28619
|
+
async createChannelConnection(orgId, input) {
|
|
28620
|
+
return this.request("POST", ENDPOINTS.CHANNEL_CONNECTIONS(orgId), input);
|
|
28621
|
+
}
|
|
28622
|
+
async listChannelConnections(orgId) {
|
|
28623
|
+
return this.request("GET", ENDPOINTS.CHANNEL_CONNECTIONS(orgId));
|
|
28624
|
+
}
|
|
28625
|
+
async getChannelConnection(orgId, connectionId) {
|
|
28626
|
+
return this.request("GET", ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId));
|
|
28627
|
+
}
|
|
28628
|
+
async updateChannelConnection(orgId, connectionId, patch) {
|
|
28629
|
+
return this.request("PATCH", ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId), patch);
|
|
28630
|
+
}
|
|
28631
|
+
async archiveChannelConnection(orgId, connectionId) {
|
|
28632
|
+
return this.request("DELETE", ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId));
|
|
28633
|
+
}
|
|
28634
|
+
async deliverChannelCredentials(orgId, connectionId, credentials) {
|
|
28635
|
+
return this.request("POST", ENDPOINTS.CHANNEL_CONNECTION_CREDENTIALS(orgId, connectionId), credentials);
|
|
28636
|
+
}
|
|
28637
|
+
async regenerateChannelIngressToken(orgId, connectionId) {
|
|
28638
|
+
return this.request("POST", ENDPOINTS.CHANNEL_CONNECTION_INGRESS_TOKEN_REGENERATE(orgId, connectionId));
|
|
28639
|
+
}
|
|
28640
|
+
/** Start a Feishu one-click provisioning session (device flow QR). */
|
|
28641
|
+
async initiateChannelProvisioning(orgId, input) {
|
|
28642
|
+
return this.request("POST", ENDPOINTS.CHANNEL_PROVISIONING(orgId), input);
|
|
28643
|
+
}
|
|
28644
|
+
/**
|
|
28645
|
+
* Lazy status poll — server-side this may forward one provider poll, so
|
|
28646
|
+
* call it at the session's `poll_interval_seconds` cadence, not faster.
|
|
28647
|
+
*/
|
|
28648
|
+
async getChannelProvisioningSession(orgId, sessionId) {
|
|
28649
|
+
return this.request("GET", ENDPOINTS.CHANNEL_PROVISIONING_SESSION(orgId, sessionId));
|
|
28650
|
+
}
|
|
28651
|
+
async cancelChannelProvisioning(orgId, sessionId) {
|
|
28652
|
+
return this.request("POST", ENDPOINTS.CHANNEL_PROVISIONING_CANCEL(orgId, sessionId));
|
|
28653
|
+
}
|
|
28654
|
+
async listChannelConversations(orgId, connectionId) {
|
|
28655
|
+
return this.request("GET", ENDPOINTS.CHANNEL_CONNECTION_CONVERSATIONS(orgId, connectionId));
|
|
28656
|
+
}
|
|
28657
|
+
async getChannelConversation(orgId, conversationId) {
|
|
28658
|
+
return this.request("GET", ENDPOINTS.CHANNEL_CONVERSATION(orgId, conversationId));
|
|
28659
|
+
}
|
|
28660
|
+
async listChannelConversationMessages(orgId, conversationId, limit) {
|
|
28661
|
+
return this.request("GET", ENDPOINTS.CHANNEL_CONVERSATION_MESSAGES(orgId, conversationId), void 0, { limit });
|
|
28662
|
+
}
|
|
28663
|
+
async setChannelConversationSession(orgId, conversationId, agentSessionId) {
|
|
28664
|
+
return this.request("PATCH", ENDPOINTS.CHANNEL_CONVERSATION_SESSION(orgId, conversationId), {
|
|
28665
|
+
agent_session_id: agentSessionId
|
|
28666
|
+
});
|
|
28667
|
+
}
|
|
28668
|
+
async getChannelMessage(orgId, messageId) {
|
|
28669
|
+
return this.request("GET", ENDPOINTS.CHANNEL_MESSAGE(orgId, messageId));
|
|
28670
|
+
}
|
|
28549
28671
|
async getExternalTriggerSchema(orgId, connectionId) {
|
|
28550
28672
|
return this.request("GET", ENDPOINTS.EXTERNAL_TRIGGER_SCHEMA(orgId, connectionId));
|
|
28551
28673
|
}
|
|
@@ -28587,6 +28709,23 @@ var ParallClient = class _ParallClient {
|
|
|
28587
28709
|
async getWiki(orgId, wikiId) {
|
|
28588
28710
|
return this.request("GET", ENDPOINTS.WIKI(orgId, wikiId));
|
|
28589
28711
|
}
|
|
28712
|
+
/** Soft-delete an entire wiki (org owner only; the default wiki is
|
|
28713
|
+
* protected → 403 WIKI_PROTECTED). Returns the deleted wiki. Recoverable
|
|
28714
|
+
* via restoreWiki within the retention window. */
|
|
28715
|
+
async deleteWiki(orgId, wikiId) {
|
|
28716
|
+
return this.request("DELETE", ENDPOINTS.WIKI(orgId, wikiId));
|
|
28717
|
+
}
|
|
28718
|
+
/** Restore a soft-deleted wiki (org owner only). 409 WIKI_PURGE_STARTED once
|
|
28719
|
+
* purge has been claimed (no longer restorable), or 404 once the row is gone.
|
|
28720
|
+
* Returns the restored wiki. */
|
|
28721
|
+
async restoreWiki(orgId, wikiId) {
|
|
28722
|
+
return this.request("POST", ENDPOINTS.WIKI_RESTORE(orgId, wikiId));
|
|
28723
|
+
}
|
|
28724
|
+
/** Recycle bin — list soft-deleted wikis for the org (owner only). */
|
|
28725
|
+
async getDeletedWikis(orgId) {
|
|
28726
|
+
const res = await this.request("GET", ENDPOINTS.WIKIS_DELETED(orgId));
|
|
28727
|
+
return res.data;
|
|
28728
|
+
}
|
|
28590
28729
|
async getWikiTree(orgId, wikiId, params) {
|
|
28591
28730
|
return this.request("GET", ENDPOINTS.WIKI_TREE(orgId, wikiId), void 0, params);
|
|
28592
28731
|
}
|
|
@@ -28620,6 +28759,15 @@ var ParallClient = class _ParallClient {
|
|
|
28620
28759
|
blob.signed_url = this.absoluteMediaUrl(blob.signed_url);
|
|
28621
28760
|
return blob;
|
|
28622
28761
|
}
|
|
28762
|
+
/**
|
|
28763
|
+
* Copy one wiki file to another path as an independent snapshot (no sync).
|
|
28764
|
+
* Used to "share a private file out": copy a file from the caller's personal
|
|
28765
|
+
* namespace (`users/{userId}/…`) into the public wiki, leaving the original
|
|
28766
|
+
* untouched. 409 `FILE_EXISTS` if `dest_path` already exists.
|
|
28767
|
+
*/
|
|
28768
|
+
async copyWikiFile(orgId, wikiId, req) {
|
|
28769
|
+
return this.request("POST", ENDPOINTS.WIKI_COPY(orgId, wikiId), req);
|
|
28770
|
+
}
|
|
28623
28771
|
async getWikiNodeSections(orgId, wikiId, params) {
|
|
28624
28772
|
return this.request("GET", ENDPOINTS.WIKI_NODE_SECTIONS(orgId, wikiId), void 0, params);
|
|
28625
28773
|
}
|
|
@@ -28893,6 +29041,19 @@ var ParallClient = class _ParallClient {
|
|
|
28893
29041
|
const resp = await this.request("GET", ENDPOINTS.COMPUTE_PRICING());
|
|
28894
29042
|
return resp.data;
|
|
28895
29043
|
}
|
|
29044
|
+
/** Runtime capability table (public) — SSOT for the create/settings interlock. */
|
|
29045
|
+
async getRuntimes() {
|
|
29046
|
+
const resp = await this.request("GET", ENDPOINTS.RUNTIMES());
|
|
29047
|
+
return resp.data;
|
|
29048
|
+
}
|
|
29049
|
+
/**
|
|
29050
|
+
* Platform model catalog (public) — DB-backed, replaces the compile-time
|
|
29051
|
+
* PLATFORM_MODELS constant. Pass includeModel to keep an agent's pinned
|
|
29052
|
+
* hidden legacy model representable (settings picker).
|
|
29053
|
+
*/
|
|
29054
|
+
async getModels(includeModel) {
|
|
29055
|
+
return this.request("GET", ENDPOINTS.MODELS(includeModel));
|
|
29056
|
+
}
|
|
28896
29057
|
// ---- Clips ----
|
|
28897
29058
|
async listClips(orgId) {
|
|
28898
29059
|
const resp = await this.request("GET", ENDPOINTS.CLIPS(orgId));
|
|
@@ -28937,6 +29098,11 @@ var ParallClient = class _ParallClient {
|
|
|
28937
29098
|
const resp = await this.request("GET", ENDPOINTS.CLIP_ONLINE(orgId));
|
|
28938
29099
|
return resp.data;
|
|
28939
29100
|
}
|
|
29101
|
+
/** Deployment-wide hosted browser runtime availability — drives whether the
|
|
29102
|
+
* create UI offers the platform-hosted placement. Fail-closed server-side. */
|
|
29103
|
+
async getBrowserRuntimeStatus(orgId) {
|
|
29104
|
+
return this.request("GET", ENDPOINTS.BROWSER_RUNTIME_STATUS(orgId));
|
|
29105
|
+
}
|
|
28940
29106
|
/** Org-wide browser-profile discovery list. Returns the sanitized
|
|
28941
29107
|
* {@link BrowserProfileListItem} shape (not the full domain model), each row
|
|
28942
29108
|
* carrying a per-viewer `can_open` control hint. */
|
|
@@ -29596,6 +29762,9 @@ function resolveStepTarget(event) {
|
|
|
29596
29762
|
if (event.type === "external_trigger" || event.targetId.startsWith("xtr_")) {
|
|
29597
29763
|
return { target_type: "external_trigger", target_id: event.targetId };
|
|
29598
29764
|
}
|
|
29765
|
+
if (event.type === "channel_message" || event.targetId.startsWith("chv_")) {
|
|
29766
|
+
return { target_type: "channel_conversation", target_id: event.targetId };
|
|
29767
|
+
}
|
|
29599
29768
|
if (event.type === "wiki_comment") {
|
|
29600
29769
|
return { target_type: "wiki", target_id: event.targetId || void 0 };
|
|
29601
29770
|
}
|
|
@@ -29639,6 +29808,9 @@ var ParallAgentGateway = class {
|
|
|
29639
29808
|
opts;
|
|
29640
29809
|
chatInfoMap = /* @__PURE__ */ new Map();
|
|
29641
29810
|
dispatchedTasks = /* @__PURE__ */ new Set();
|
|
29811
|
+
// connection id → provider alias, for channel_message prompt labeling
|
|
29812
|
+
// (stable mapping; avoids one connection fetch per inbound message).
|
|
29813
|
+
channelConnectionProviders = /* @__PURE__ */ new Map();
|
|
29642
29814
|
dispatchedMessages = /* @__PURE__ */ new Set();
|
|
29643
29815
|
forkStates = /* @__PURE__ */ new Map();
|
|
29644
29816
|
dispatchState = {
|
|
@@ -29812,6 +29984,18 @@ var ParallAgentGateway = class {
|
|
|
29812
29984
|
} catch (err) {
|
|
29813
29985
|
this.opts.log?.error(`external trigger dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29814
29986
|
}
|
|
29987
|
+
} else if (data.event_type === "channel_message") {
|
|
29988
|
+
if (!data.source_id)
|
|
29989
|
+
return;
|
|
29990
|
+
try {
|
|
29991
|
+
const dispatched = await this.fetchAndHandleChannelMessage(data.source_id);
|
|
29992
|
+
if (dispatched) {
|
|
29993
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29994
|
+
});
|
|
29995
|
+
}
|
|
29996
|
+
} catch (err) {
|
|
29997
|
+
this.opts.log?.error(`channel message dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29998
|
+
}
|
|
29815
29999
|
} else if (data.event_type === "approval_decided") {
|
|
29816
30000
|
if (!data.source_id)
|
|
29817
30001
|
return;
|
|
@@ -29890,12 +30074,17 @@ var ParallAgentGateway = class {
|
|
|
29890
30074
|
target_type: target.target_type,
|
|
29891
30075
|
target_id: target.target_id,
|
|
29892
30076
|
content: {
|
|
29893
|
-
trigger_type: event.type === "task" ? "task_assign" : event.type === "task_comment" ? "task_comment" : event.type === "wiki_comment" ? "wiki_comment" : event.type === "schedule" ? "schedule_fire" : event.type === "external_trigger" ? "external_trigger" : event.type === "approval" ? "approval_decided" : "mention",
|
|
30077
|
+
trigger_type: event.type === "task" ? "task_assign" : event.type === "task_comment" ? "task_comment" : event.type === "wiki_comment" ? "wiki_comment" : event.type === "schedule" ? "schedule_fire" : event.type === "external_trigger" ? "external_trigger" : event.type === "channel_message" ? "channel_message" : event.type === "approval" ? "approval_decided" : "mention",
|
|
29894
30078
|
trigger_ref: event.type === "task" ? { task_id: event.targetId } : event.type === "task_comment" ? { comment_id: event.messageId, task_id: event.targetId } : event.type === "wiki_comment" ? { comment_id: event.messageId, target_uri: event.replyTargetUri } : event.type === "schedule" ? { schedule_id: event.targetId, run_id: event.messageId } : event.type === "external_trigger" ? {
|
|
29895
30079
|
trigger_id: event.targetId,
|
|
29896
30080
|
run_id: event.messageId,
|
|
29897
30081
|
connection_id: event.externalConnectionId,
|
|
29898
30082
|
ingress_event_id: event.externalIngressEventId
|
|
30083
|
+
} : event.type === "channel_message" ? {
|
|
30084
|
+
conversation_id: event.targetId,
|
|
30085
|
+
channel_message_id: event.messageId,
|
|
30086
|
+
provider: event.channelProvider,
|
|
30087
|
+
external_conversation_id: event.channelExternalConversationId
|
|
29899
30088
|
} : event.type === "approval" ? { approval_id: event.messageId } : { message_id: event.messageId },
|
|
29900
30089
|
sender_id: event.senderId,
|
|
29901
30090
|
sender_name: event.senderName,
|
|
@@ -30146,7 +30335,15 @@ var ParallAgentGateway = class {
|
|
|
30146
30335
|
context: dispatchContext
|
|
30147
30336
|
})) {
|
|
30148
30337
|
if (runtimeEvent.type === "runtime_session") {
|
|
30338
|
+
const priorAgentSessionId = binding?.agentSessionId;
|
|
30149
30339
|
binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath);
|
|
30340
|
+
if (event.targetType === "channel_conversation" && binding.agentSessionId !== priorAgentSessionId) {
|
|
30341
|
+
try {
|
|
30342
|
+
await this.opts.client.setChannelConversationSession(this.opts.config.org_id, event.targetId, binding.agentSessionId);
|
|
30343
|
+
} catch (err) {
|
|
30344
|
+
this.opts.log?.warn(`failed to record session mapping for channel conversation ${event.targetId}: ${String(err)}`);
|
|
30345
|
+
}
|
|
30346
|
+
}
|
|
30150
30347
|
if (!inputStepsCreated) {
|
|
30151
30348
|
if (earlierEvents.length > 0) {
|
|
30152
30349
|
await this.createInputStepsForEarlierEvents(binding.agentSessionId, earlierEvents);
|
|
@@ -30949,6 +31146,75 @@ var ParallAgentGateway = class {
|
|
|
30949
31146
|
return true;
|
|
30950
31147
|
return this.handleExternalTriggerRun(run);
|
|
30951
31148
|
}
|
|
31149
|
+
// fetchAndHandleChannelMessage resolves a channel_message dispatch to its
|
|
31150
|
+
// durable ChannelMessage + conversation and hands it to the inbound
|
|
31151
|
+
// pipeline. targetId = the ChannelConversation id, so per-conversation
|
|
31152
|
+
// multi-turn continuity rides the same per-target session mechanics as
|
|
31153
|
+
// chats. Design: docs/engineering-design/external-im-channel-design.md.
|
|
31154
|
+
async fetchAndHandleChannelMessage(messageId) {
|
|
31155
|
+
if (this.shuttingDown)
|
|
31156
|
+
return false;
|
|
31157
|
+
const claimKey = `channel_message:${messageId}`;
|
|
31158
|
+
if (!this.tryClaimMessage(claimKey))
|
|
31159
|
+
return false;
|
|
31160
|
+
let msg = null;
|
|
31161
|
+
let conv = null;
|
|
31162
|
+
try {
|
|
31163
|
+
msg = await this.opts.client.getChannelMessage(this.opts.config.org_id, messageId);
|
|
31164
|
+
conv = await this.opts.client.getChannelConversation(this.opts.config.org_id, msg.conversation_id);
|
|
31165
|
+
} catch (err) {
|
|
31166
|
+
const status = err?.status;
|
|
31167
|
+
if (status === 404) {
|
|
31168
|
+
this.opts.log?.warn(`channel message ${messageId} not accessible (404), acking stale dispatch`);
|
|
31169
|
+
return true;
|
|
31170
|
+
}
|
|
31171
|
+
this.dispatchedMessages.delete(claimKey);
|
|
31172
|
+
this.opts.log?.warn(`channel message fetch failed for ${messageId}, leaving pending: ${String(err)}`);
|
|
31173
|
+
return false;
|
|
31174
|
+
}
|
|
31175
|
+
if (!msg || !conv) {
|
|
31176
|
+
return true;
|
|
31177
|
+
}
|
|
31178
|
+
this.opts.log?.info(`channel message: ${msg.id} (conversation ${conv.id})`);
|
|
31179
|
+
let provider = this.channelConnectionProviders.get(conv.connection_id);
|
|
31180
|
+
if (!provider) {
|
|
31181
|
+
try {
|
|
31182
|
+
const connection = await this.opts.client.getChannelConnection(this.opts.config.org_id, conv.connection_id);
|
|
31183
|
+
provider = connection.provider;
|
|
31184
|
+
this.channelConnectionProviders.set(conv.connection_id, provider);
|
|
31185
|
+
} catch {
|
|
31186
|
+
provider = void 0;
|
|
31187
|
+
}
|
|
31188
|
+
}
|
|
31189
|
+
const event = {
|
|
31190
|
+
type: "channel_message",
|
|
31191
|
+
targetId: conv.id,
|
|
31192
|
+
targetName: conv.external_user_name || conv.external_conversation_id,
|
|
31193
|
+
targetType: "channel_conversation",
|
|
31194
|
+
senderId: msg.external_user_id || "external",
|
|
31195
|
+
senderName: msg.external_user_name || msg.external_user_id || "external user",
|
|
31196
|
+
messageId: msg.id,
|
|
31197
|
+
body: msg.text,
|
|
31198
|
+
sentAt: msg.received_at,
|
|
31199
|
+
channelProvider: provider,
|
|
31200
|
+
channelConversationType: conv.conversation_type || void 0,
|
|
31201
|
+
channelExternalConversationId: conv.external_conversation_id,
|
|
31202
|
+
channelExternalMessageId: msg.external_message_id,
|
|
31203
|
+
ackSourceType: "channel_message",
|
|
31204
|
+
ackSourceId: msg.id
|
|
31205
|
+
};
|
|
31206
|
+
let dispatched;
|
|
31207
|
+
try {
|
|
31208
|
+
dispatched = await this.handleInboundEvent(event);
|
|
31209
|
+
} catch (err) {
|
|
31210
|
+
this.dispatchedMessages.delete(claimKey);
|
|
31211
|
+
throw err;
|
|
31212
|
+
}
|
|
31213
|
+
if (!dispatched) {
|
|
31214
|
+
this.dispatchedMessages.delete(claimKey);
|
|
31215
|
+
}
|
|
31216
|
+
return dispatched;
|
|
31217
|
+
}
|
|
30952
31218
|
async handleExternalTriggerRun(run) {
|
|
30953
31219
|
if (this.shuttingDown)
|
|
30954
31220
|
return false;
|
|
@@ -31100,6 +31366,8 @@ var ParallAgentGateway = class {
|
|
|
31100
31366
|
dispatched = await this.fetchAndHandleScheduleFire(item.source_id, item.actor_id);
|
|
31101
31367
|
} else if (item.event_type === "external_trigger" && item.source_id) {
|
|
31102
31368
|
dispatched = await this.fetchAndHandleExternalTriggerRun(item.source_id);
|
|
31369
|
+
} else if (item.event_type === "channel_message" && item.source_id) {
|
|
31370
|
+
dispatched = await this.fetchAndHandleChannelMessage(item.source_id);
|
|
31103
31371
|
} else if (item.event_type === "approval_decided" && item.source_id) {
|
|
31104
31372
|
dispatched = await this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null);
|
|
31105
31373
|
} else if (item.event_type === "message" && item.source_id && item.chat_id) {
|
|
@@ -33117,9 +33385,9 @@ To respond, you **must** use the Parall CLI via the exec (Bash) tool. If \`paral
|
|
|
33117
33385
|
|
|
33118
33386
|
### Event types and where to reply
|
|
33119
33387
|
|
|
33120
|
-
- **\`[Event: message.new]\`** \u2014 includes \`[Chat: ... (prll://cht_xxx)]\`. Reply into that chat:
|
|
33388
|
+
- **\`[Event: message.new]\`** \u2014 includes \`[Chat: ... (prll://cht_xxx)]\`. Reply into that chat. Write the reply with your file tool first (shell-safe), then send it \u2014 don't wrap message content in \`--text "..."\`, since the shell expands \`$1,000\`\u2192\`,000\` and runs \`$(...)\` inside double quotes:
|
|
33121
33389
|
|
|
33122
|
-
parall messages send prll://cht_xxx --text
|
|
33390
|
+
parall messages send prll://cht_xxx --text-file /tmp/reply.md
|
|
33123
33391
|
|
|
33124
33392
|
- **\`[Event: task.assigned]\` / \`[Event: task.comment.created]\`** \u2014 includes \`[Task: ... (prll://tsk_xxx)]\`. Act on the task; use task CLI subcommands (\`tasks update\`, \`tasks comment\`). See the \`parall-tasks\` skill.
|
|
33125
33393
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/parall",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.37.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/
|
|
20
|
-
"@parall/
|
|
19
|
+
"@parall/sdk": "1.37.0",
|
|
20
|
+
"@parall/agent-core": "1.37.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/node": "^22.0.0",
|
|
@@ -103,16 +103,35 @@ original discussion and its approval/rejection IS the precedent.
|
|
|
103
103
|
|
|
104
104
|
Each `[Event: message.new]` includes `[Chat: ... (prll://cht_xxx)]` — use that chat URI to reply.
|
|
105
105
|
|
|
106
|
+
> **How you pass the message body matters — your command runs through a shell.**
|
|
107
|
+
> Inside double quotes the shell expands `$`, backticks, and `$(...)` *before*
|
|
108
|
+
> the CLI sees them: `--text "That costs $1,000"` sends `That costs ,000`, and
|
|
109
|
+
> `--text "$(cmd)"` runs `cmd`. Single quotes instead break on apostrophes
|
|
110
|
+
> (`I'm`, `don't`). So do **not** wrap real message content in quotes — pass it
|
|
111
|
+
> through `--text-file` (a written file, or a quoted heredoc `<<'EOF'` that
|
|
112
|
+
> disables all expansion). Reserve `--text "..."` for short literals with no
|
|
113
|
+
> `$`, backtick, or apostrophe.
|
|
114
|
+
|
|
106
115
|
```bash
|
|
107
|
-
#
|
|
108
|
-
|
|
116
|
+
# One-off reply → quoted heredoc into stdin. The quoted delimiter <<'EOF'
|
|
117
|
+
# disables ALL shell expansion, so $, backticks and apostrophes pass verbatim.
|
|
118
|
+
parall messages send prll://cht_xxx --text-file - <<'PARALL_EOF'
|
|
119
|
+
Sure — that's $1,000, and $(whoami) stays literal. I'm on it.
|
|
120
|
+
PARALL_EOF
|
|
121
|
+
|
|
122
|
+
# Longer / multi-line reply → write it with your file tool (no shell touches
|
|
123
|
+
# the body), then point --text-file at the file.
|
|
124
|
+
parall messages send prll://cht_xxx --text-file /tmp/reply.md
|
|
125
|
+
|
|
126
|
+
# Short literal with no $, backtick, or apostrophe → --text is fine.
|
|
127
|
+
parall messages send prll://cht_xxx --text "On it"
|
|
109
128
|
|
|
110
|
-
# Direct message by user URI or display name
|
|
111
|
-
parall dm prll://usr_xxx --text
|
|
129
|
+
# Direct message by user URI or display name (same --text-file / heredoc rules)
|
|
130
|
+
parall dm prll://usr_xxx --text-file /tmp/reply.md
|
|
112
131
|
parall dm "Alice" --text "Hello"
|
|
113
132
|
|
|
114
133
|
# Thread reply
|
|
115
|
-
parall messages send prll://cht_xxx --text
|
|
134
|
+
parall messages send prll://cht_xxx --text-file /tmp/reply.md --thread-root-id 01JWC...
|
|
116
135
|
|
|
117
136
|
# FYI message (no response expected — the recipient sees `[Hint: no_reply]`)
|
|
118
137
|
parall messages send prll://cht_xxx --text "FYI: done" --no-reply
|
|
@@ -144,7 +163,9 @@ parall messages send prll://cht_xxx --attachment att_xxx --text "See attached"
|
|
|
144
163
|
parall dm "Alice" --file /tmp/report.pdf --text "Report attached"
|
|
145
164
|
```
|
|
146
165
|
|
|
147
|
-
`--file` and `--attachment` are mutually exclusive. `--text`
|
|
166
|
+
`--file` and `--attachment` are mutually exclusive. A caption (`--text` for
|
|
167
|
+
short literals, or `--text-file` for anything with `$`, backticks, or quotes)
|
|
168
|
+
can be combined with either.
|
|
148
169
|
|
|
149
170
|
## Approvals
|
|
150
171
|
|
package/src/hooks.ts
CHANGED
|
@@ -90,9 +90,9 @@ To respond, you **must** use the Parall CLI via the exec (Bash) tool. If \`paral
|
|
|
90
90
|
|
|
91
91
|
### Event types and where to reply
|
|
92
92
|
|
|
93
|
-
- **\`[Event: message.new]\`** — includes \`[Chat: ... (prll://cht_xxx)]\`. Reply into that chat:
|
|
93
|
+
- **\`[Event: message.new]\`** — includes \`[Chat: ... (prll://cht_xxx)]\`. Reply into that chat. Write the reply with your file tool first (shell-safe), then send it — don't wrap message content in \`--text "..."\`, since the shell expands \`$1,000\`→\`,000\` and runs \`$(...)\` inside double quotes:
|
|
94
94
|
|
|
95
|
-
parall messages send prll://cht_xxx --text
|
|
95
|
+
parall messages send prll://cht_xxx --text-file /tmp/reply.md
|
|
96
96
|
|
|
97
97
|
- **\`[Event: task.assigned]\` / \`[Event: task.comment.created]\`** — includes \`[Task: ... (prll://tsk_xxx)]\`. Act on the task; use task CLI subcommands (\`tasks update\`, \`tasks comment\`). See the \`parall-tasks\` skill.
|
|
98
98
|
|