@parall/agent-core 1.24.0 → 1.26.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.
Files changed (40) hide show
  1. package/dist/bridge-workspace.d.ts +1 -1
  2. package/dist/bridge-workspace.d.ts.map +1 -1
  3. package/dist/bridge-workspace.js +4 -3
  4. package/dist/dispatch-adapter.d.ts +2 -0
  5. package/dist/dispatch-adapter.d.ts.map +1 -1
  6. package/dist/gateway-base.d.ts +7 -0
  7. package/dist/gateway-base.d.ts.map +1 -1
  8. package/dist/gateway-base.js +95 -29
  9. package/dist/index.d.ts +3 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +2 -0
  12. package/dist/platform-config.d.ts +23 -0
  13. package/dist/platform-config.d.ts.map +1 -0
  14. package/dist/platform-config.js +105 -0
  15. package/dist/skills/index.d.ts +14 -0
  16. package/dist/skills/index.d.ts.map +1 -0
  17. package/dist/skills/index.js +44 -0
  18. package/dist/skills/parall-platform.d.ts +2 -0
  19. package/dist/skills/parall-platform.d.ts.map +1 -0
  20. package/dist/skills/parall-platform.js +161 -0
  21. package/dist/skills/parall-schedules.d.ts +2 -0
  22. package/dist/skills/parall-schedules.d.ts.map +1 -0
  23. package/dist/skills/parall-schedules.js +80 -0
  24. package/dist/skills/parall-tasks.d.ts +2 -0
  25. package/dist/skills/parall-tasks.d.ts.map +1 -0
  26. package/dist/skills/parall-tasks.js +65 -0
  27. package/dist/skills/parall-wiki.d.ts +2 -0
  28. package/dist/skills/parall-wiki.d.ts.map +1 -0
  29. package/dist/skills/parall-wiki.js +131 -0
  30. package/package.json +2 -2
  31. package/src/bridge-workspace.ts +4 -3
  32. package/src/dispatch-adapter.ts +2 -0
  33. package/src/gateway-base.ts +96 -27
  34. package/src/index.ts +3 -0
  35. package/src/platform-config.ts +159 -0
  36. package/src/skills/index.ts +52 -0
  37. package/src/skills/parall-platform.ts +161 -0
  38. package/src/skills/parall-schedules.ts +80 -0
  39. package/src/skills/parall-tasks.ts +65 -0
  40. package/src/skills/parall-wiki.ts +131 -0
@@ -98,6 +98,8 @@ export type ParallGatewayOptions = {
98
98
  // abort before forcing WS disconnect. Pod termination grace period should
99
99
  // be at least this + a few seconds for the remaining cleanup work.
100
100
  shutdownDeadlineMs?: number;
101
+ contextFilePathForSession?: (sessionKey: string) => string | undefined;
102
+ /** @deprecated Use contextFilePathForSession. Kept for runtimes that haven't migrated. */
101
103
  stepIdFilePathForSession?: (sessionKey: string) => string | undefined;
102
104
  onConfigUpdate?: (data: AgentConfigUpdateData) => Promise<void> | void;
103
105
  onSessionReady?: (state: { activeSessionId?: string; ws: ParallWs; runtimeKey: string }) => Promise<void> | void;
@@ -272,6 +274,16 @@ export class ParallAgentGateway {
272
274
  } catch (err) {
273
275
  this.opts.log?.error(`parall[${this.opts.accountId}]: task comment dispatch failed for ${data.source_id}: ${String(err)}`);
274
276
  }
277
+ } else if (data.event_type === "task_update") {
278
+ if (!data.task_id) return;
279
+ try {
280
+ const dispatched = await this.handleTaskDispatch(data.task_id, data.source_id ?? data.task_id, { allowCreator: true });
281
+ if (dispatched) {
282
+ this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {});
283
+ }
284
+ } catch (err) {
285
+ this.opts.log?.error(`parall[${this.opts.accountId}]: task update dispatch failed for ${data.task_id}: ${String(err)}`);
286
+ }
275
287
  } else if (data.event_type === "schedule.fire") {
276
288
  if (!data.source_id) return;
277
289
  try {
@@ -356,6 +368,7 @@ export class ParallAgentGateway {
356
368
  chatId: event.type === "message" ? event.targetId : undefined,
357
369
  triggerMessageId: event.messageId,
358
370
  noReply: event.noReply ?? false,
371
+ contextFilePath: this.opts.contextFilePathForSession?.(sessionKey),
359
372
  stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey),
360
373
  client: this.opts.client,
361
374
  log: this.opts.log,
@@ -391,7 +404,7 @@ export class ParallAgentGateway {
391
404
  }
392
405
  }
393
406
 
394
- private async createRuntimeStep(event: ParallEvent, runtimeEvent: RuntimeEvent, stepIdFilePath?: string) {
407
+ private async createRuntimeStep(event: ParallEvent, runtimeEvent: RuntimeEvent, stepIdFilePath?: string, contextFilePath?: string) {
395
408
  if (!this.activeSessionId) return;
396
409
 
397
410
  const target = resolveStepTarget(event);
@@ -436,7 +449,9 @@ export class ParallAgentGateway {
436
449
  group_key: runtimeEvent.groupKey,
437
450
  runtime_key: runtimeEvent.callId,
438
451
  });
439
- if (stepIdFilePath) {
452
+ if (contextFilePath) {
453
+ this.updateContextFileStepId(contextFilePath, step.id);
454
+ } else if (stepIdFilePath) {
440
455
  this.writeStepIdFile(stepIdFilePath, step.id);
441
456
  }
442
457
  break;
@@ -457,7 +472,9 @@ export class ParallAgentGateway {
457
472
  },
458
473
  group_key: runtimeEvent.groupKey,
459
474
  });
460
- if (stepIdFilePath) {
475
+ if (contextFilePath) {
476
+ this.updateContextFileStepId(contextFilePath, null);
477
+ } else if (stepIdFilePath) {
461
478
  this.clearStepIdFile(stepIdFilePath);
462
479
  }
463
480
  break;
@@ -477,6 +494,27 @@ export class ParallAgentGateway {
477
494
  }
478
495
  }
479
496
 
497
+ private writeContextFile(filePath: string, ctx: Record<string, unknown>) {
498
+ try {
499
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
500
+ fs.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
501
+ } catch (err) {
502
+ this.opts.log?.warn(`parall[${this.opts.accountId}]: failed to write context file ${filePath}: ${String(err)}`);
503
+ }
504
+ }
505
+
506
+ private updateContextFileStepId(filePath: string, stepId: string | null) {
507
+ try {
508
+ const raw = fs.readFileSync(filePath, "utf8");
509
+ const ctx = JSON.parse(raw);
510
+ ctx.step_id = stepId;
511
+ fs.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
512
+ } catch (err) {
513
+ this.opts.log?.warn(`parall[${this.opts.accountId}]: failed to update context file step_id ${filePath}: ${String(err)}`);
514
+ }
515
+ }
516
+
517
+ /** @deprecated Use writeContextFile / updateContextFileStepId. */
480
518
  private writeStepIdFile(filePath: string, stepId: string) {
481
519
  try {
482
520
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
@@ -486,6 +524,7 @@ export class ParallAgentGateway {
486
524
  }
487
525
  }
488
526
 
527
+ /** @deprecated Use writeContextFile / updateContextFileStepId. */
489
528
  private clearStepIdFile(filePath: string) {
490
529
  try {
491
530
  fs.writeFileSync(filePath, "", "utf8");
@@ -526,8 +565,19 @@ export class ParallAgentGateway {
526
565
  setDispatchNoReply(sessionKey, event.noReply ?? false);
527
566
 
528
567
  const dispatchContext = this.buildDispatchContext(event, sessionKey);
568
+ const contextFilePath = dispatchContext.contextFilePath;
529
569
  const stepIdFilePath = dispatchContext.stepIdFilePath;
530
570
 
571
+ if (contextFilePath) {
572
+ this.writeContextFile(contextFilePath, {
573
+ session_id: dispatchContext.sessionId ?? null,
574
+ chat_id: dispatchContext.chatId ?? null,
575
+ trigger_message_id: dispatchContext.triggerMessageId ?? null,
576
+ no_reply: dispatchContext.noReply,
577
+ step_id: null,
578
+ });
579
+ }
580
+
531
581
  // sync: no await between the shuttingDown check above and this increment
532
582
  // — JS event loop is single-threaded, so shutdown() cannot interleave
533
583
  // here and miss our in-flight count.
@@ -557,19 +607,21 @@ export class ParallAgentGateway {
557
607
  sessionKey,
558
608
  context: dispatchContext,
559
609
  })) {
560
- await this.createRuntimeStep(event, runtimeEvent, stepIdFilePath);
610
+ await this.createRuntimeStep(event, runtimeEvent, stepIdFilePath, contextFilePath);
561
611
  }
562
612
  } catch (err) {
563
613
  await this.createRuntimeStep(event, {
564
614
  type: "error",
565
615
  message: `Dispatch failed: ${String(err)}`,
566
- }, stepIdFilePath);
616
+ }, stepIdFilePath, contextFilePath);
567
617
  throw err;
568
618
  } finally {
569
619
  clearSessionMessageId(sessionKey);
570
620
  clearDispatchMessageId(sessionKey);
571
621
  clearDispatchNoReply(sessionKey);
572
- if (stepIdFilePath) {
622
+ if (contextFilePath) {
623
+ this.updateContextFileStepId(contextFilePath, null);
624
+ } else if (stepIdFilePath) {
573
625
  this.clearStepIdFile(stepIdFilePath);
574
626
  }
575
627
  if (this.activeSessionId) {
@@ -770,9 +822,13 @@ export class ParallAgentGateway {
770
822
  return dispatched;
771
823
  }
772
824
 
773
- case "buffer-main":
825
+ case "buffer-main": {
826
+ if (this.shuttingDown) {
827
+ return false;
828
+ }
774
829
  this.dispatchState.mainBuffer.push(event);
775
830
  return false;
831
+ }
776
832
 
777
833
  case "buffer-fork": {
778
834
  const activeFork = this.forkStates.get(event.targetId);
@@ -959,6 +1015,30 @@ export class ParallAgentGateway {
959
1015
  return dispatched;
960
1016
  }
961
1017
 
1018
+ private async handleTaskDispatch(
1019
+ taskId: string,
1020
+ ackSourceId?: string,
1021
+ opts: { allowCreator?: boolean } = {},
1022
+ ): Promise<boolean> {
1023
+ let task: Awaited<ReturnType<typeof this.opts.client.getTask>> | null = null;
1024
+ try {
1025
+ task = await this.opts.client.getTask(this.opts.config.org_id, taskId);
1026
+ } catch (err: unknown) {
1027
+ const status = (err as { status?: number })?.status;
1028
+ if (status === 404) return true;
1029
+ throw err;
1030
+ }
1031
+ const isAssignee = task.assignee_id === this.opts.agentUserId;
1032
+ const isCreatorUpdate = opts.allowCreator === true && task.creator_id === this.opts.agentUserId;
1033
+ if (!isAssignee && !isCreatorUpdate) {
1034
+ this.opts.log?.info(
1035
+ `parall[${this.opts.accountId}]: skipping stale task dispatch ${ackSourceId ?? taskId} — assigned to ${task.assignee_id}, creator ${task.creator_id}`,
1036
+ );
1037
+ return true;
1038
+ }
1039
+ return this.handleTaskAssignment(task, ackSourceId);
1040
+ }
1041
+
962
1042
  private async handleTaskComment(
963
1043
  commentId: string,
964
1044
  taskId: string,
@@ -1134,29 +1214,18 @@ export class ParallAgentGateway {
1134
1214
  try {
1135
1215
  let dispatched = false;
1136
1216
  if (item.event_type === "task_assign" && item.task_id) {
1137
- let task: Awaited<ReturnType<typeof this.opts.client.getTask>> | null = null;
1138
- let taskFetchFailed = false;
1139
1217
  try {
1140
- task = await this.opts.client.getTask(this.opts.config.org_id, item.task_id);
1218
+ dispatched = await this.handleTaskDispatch(item.task_id, item.source_id ?? item.task_id);
1141
1219
  } catch (err: unknown) {
1142
- const status = (err as { status?: number })?.status;
1143
- if (status === 404) {
1144
- task = null;
1145
- } else {
1146
- taskFetchFailed = true;
1147
- this.opts.log?.warn(`parall[${this.opts.accountId}]: catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
1148
- }
1220
+ this.opts.log?.warn(`parall[${this.opts.accountId}]: catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
1221
+ continue;
1149
1222
  }
1150
- if (taskFetchFailed) continue;
1151
- if (task) {
1152
- if (task.assignee_id !== this.opts.agentUserId) {
1153
- this.opts.log?.info(`parall[${this.opts.accountId}]: skipping stale task dispatch ${item.id} — reassigned to ${task.assignee_id}`);
1154
- this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {});
1155
- continue;
1156
- }
1157
- dispatched = await this.handleTaskAssignment(task);
1158
- } else {
1159
- dispatched = true;
1223
+ } else if (item.event_type === "task_update" && item.task_id) {
1224
+ try {
1225
+ dispatched = await this.handleTaskDispatch(item.task_id, item.source_id ?? item.task_id, { allowCreator: true });
1226
+ } catch (err: unknown) {
1227
+ this.opts.log?.warn(`parall[${this.opts.accountId}]: catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
1228
+ continue;
1160
1229
  }
1161
1230
  } else if (item.event_type === "task_comment" && item.source_id && item.task_id) {
1162
1231
  dispatched = await this.handleTaskComment(item.source_id, item.task_id, item.actor_id, item.delivery_reason);
package/src/index.ts CHANGED
@@ -6,3 +6,6 @@ export * from "./prompt-fragments.js";
6
6
  export * from "./bridge-workspace.js";
7
7
  export * from "./dispatch-adapter.js";
8
8
  export * from "./gateway-base.js";
9
+ export * from "./platform-config.js";
10
+ export { writeSkillFiles, buildSkillReferences, SKILLS } from "./skills/index.js";
11
+ export type { SkillMeta } from "./skills/index.js";
@@ -0,0 +1,159 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import type { ParallClient, PlatformConfigResponse } from "@parall/sdk";
4
+ import type { GatewayLogger } from "./dispatch-adapter.js";
5
+
6
+ export interface PlatformDefaults {
7
+ model: string | null;
8
+ thinkingEffort: string | null;
9
+ }
10
+
11
+ export interface PlatformConfigManager {
12
+ fetch(): Promise<PlatformDefaults>;
13
+ current(): PlatformDefaults;
14
+ rawConfig(): Record<string, unknown> | null;
15
+ }
16
+
17
+ export interface PlatformManagementProfile {
18
+ machine_id?: string | null;
19
+ model_management?: string | null;
20
+ }
21
+
22
+ export function isPlatformManagedProfile(profile: PlatformManagementProfile | null | undefined): boolean {
23
+ return profile?.machine_id != null || profile?.model_management === "platform";
24
+ }
25
+
26
+ interface PlatformModelDef {
27
+ id?: unknown;
28
+ runtime_names?: unknown;
29
+ }
30
+
31
+ interface CachedPlatformConfig {
32
+ version: string;
33
+ config: Record<string, unknown>;
34
+ fetchedAt: string;
35
+ }
36
+
37
+ const CACHE_FILENAME = "parall-platform-config.json";
38
+ const SUPPORTED_SCHEMA_VERSION = 1;
39
+
40
+ function cachePath(stateDir: string): string {
41
+ return path.join(stateDir, CACHE_FILENAME);
42
+ }
43
+
44
+ function loadCache(stateDir: string): CachedPlatformConfig | null {
45
+ try {
46
+ const raw = fs.readFileSync(cachePath(stateDir), "utf-8");
47
+ return JSON.parse(raw) as CachedPlatformConfig;
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+
53
+ function saveCache(stateDir: string, response: PlatformConfigResponse): void {
54
+ const cached: CachedPlatformConfig = {
55
+ version: response.version,
56
+ config: response.config,
57
+ fetchedAt: new Date().toISOString(),
58
+ };
59
+ const filePath = cachePath(stateDir);
60
+ const tmpPath = `${filePath}.tmp`;
61
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
62
+ fs.writeFileSync(tmpPath, JSON.stringify(cached, null, 2), "utf-8");
63
+ fs.renameSync(tmpPath, filePath);
64
+ }
65
+
66
+ function runtimeModelName(
67
+ canonicalModel: string,
68
+ runtimeType: string | undefined,
69
+ config: Record<string, unknown>,
70
+ ): string | null {
71
+ const runtime = runtimeType?.trim();
72
+ if (!runtime || runtime === "openclaw") return canonicalModel;
73
+
74
+ const models = (config.models ?? {}) as Record<string, unknown>;
75
+ const providers = (models.providers ?? {}) as Record<string, unknown>;
76
+ const parall = (providers.parall ?? {}) as Record<string, unknown>;
77
+ const catalog = Array.isArray(parall.models) ? (parall.models as PlatformModelDef[]) : [];
78
+ const match = catalog.find((model) => model.id === canonicalModel);
79
+ const runtimeNames = (match?.runtime_names ?? {}) as Record<string, unknown>;
80
+ const runtimeName = runtimeNames[runtime];
81
+ return typeof runtimeName === "string" && runtimeName ? runtimeName : null;
82
+ }
83
+
84
+ function extractDefaults(config: Record<string, unknown>, runtimeType?: string): PlatformDefaults {
85
+ const agents = (config.agents ?? {}) as Record<string, unknown>;
86
+ const defaults = (agents.defaults ?? {}) as Record<string, unknown>;
87
+
88
+ let model: string | null = null;
89
+ if (typeof defaults.model === "string" && defaults.model) {
90
+ const canonicalModel = defaults.model.replace(/^parall\//, "");
91
+ model = runtimeModelName(canonicalModel, runtimeType, config);
92
+ }
93
+
94
+ let thinkingEffort: string | null = null;
95
+ if (typeof defaults.thinking_effort === "string" && defaults.thinking_effort) {
96
+ thinkingEffort = defaults.thinking_effort;
97
+ }
98
+
99
+ return { model, thinkingEffort };
100
+ }
101
+
102
+ export function createPlatformConfigManager(opts: {
103
+ client: ParallClient;
104
+ stateDir: string;
105
+ runtimeType?: string;
106
+ log?: GatewayLogger;
107
+ }): PlatformConfigManager {
108
+ const { client, stateDir, runtimeType, log } = opts;
109
+ let cachedVersion: string | undefined;
110
+ let currentDefaults: PlatformDefaults = { model: null, thinkingEffort: null };
111
+ let currentRawConfig: Record<string, unknown> | null = null;
112
+
113
+ const cached = loadCache(stateDir);
114
+ if (cached) {
115
+ cachedVersion = cached.version;
116
+ currentRawConfig = cached.config;
117
+ currentDefaults = extractDefaults(cached.config, runtimeType);
118
+ }
119
+
120
+ return {
121
+ async fetch(): Promise<PlatformDefaults> {
122
+ let fresh: PlatformConfigResponse | null = null;
123
+ try {
124
+ fresh = await client.getPlatformConfig(cachedVersion);
125
+ } catch (err) {
126
+ if (cached) {
127
+ log?.warn(`platform config fetch failed, using cached version ${cachedVersion}: ${String(err)}`);
128
+ return currentDefaults;
129
+ }
130
+ log?.warn(`platform config fetch failed and no cache available: ${String(err)}`);
131
+ return currentDefaults;
132
+ }
133
+
134
+ if (fresh === null) {
135
+ return currentDefaults;
136
+ }
137
+
138
+ if (fresh.schema_version !== undefined && fresh.schema_version > SUPPORTED_SCHEMA_VERSION) {
139
+ log?.warn(`platform config schema_version ${fresh.schema_version} > supported (${SUPPORTED_SCHEMA_VERSION}), keeping current`);
140
+ return currentDefaults;
141
+ }
142
+
143
+ log?.info(`platform config updated to version ${fresh.version}`);
144
+ saveCache(stateDir, fresh);
145
+ cachedVersion = fresh.version;
146
+ currentRawConfig = fresh.config;
147
+ currentDefaults = extractDefaults(fresh.config, runtimeType);
148
+ return currentDefaults;
149
+ },
150
+
151
+ current(): PlatformDefaults {
152
+ return currentDefaults;
153
+ },
154
+
155
+ rawConfig(): Record<string, unknown> | null {
156
+ return currentRawConfig;
157
+ },
158
+ };
159
+ }
@@ -0,0 +1,52 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+
4
+ export { PARALL_PLATFORM_SKILL } from "./parall-platform.js";
5
+ export { PARALL_TASKS_SKILL } from "./parall-tasks.js";
6
+ export { PARALL_WIKI_SKILL } from "./parall-wiki.js";
7
+ export { PARALL_SCHEDULES_SKILL } from "./parall-schedules.js";
8
+
9
+ import { PARALL_PLATFORM_SKILL } from "./parall-platform.js";
10
+ import { PARALL_TASKS_SKILL } from "./parall-tasks.js";
11
+ import { PARALL_WIKI_SKILL } from "./parall-wiki.js";
12
+ import { PARALL_SCHEDULES_SKILL } from "./parall-schedules.js";
13
+
14
+ export type SkillMeta = { name: string; description: string; content: string };
15
+
16
+ export const SKILLS: SkillMeta[] = [
17
+ {
18
+ name: "parall-platform",
19
+ description: "Parall platform queries and lightweight agent provisioning: list org members, agents, chats, read message history, check identity, or create another agent. Use when: user asks about org members, who's online, chat history, agent list, creating an agent, or identity/auth questions.",
20
+ content: PARALL_PLATFORM_SKILL,
21
+ },
22
+ {
23
+ name: "parall-tasks",
24
+ description: "Parall task operations: create, update, comment on, and query tasks and projects. Use when: user asks to create a task, update task status, add comments, list tasks, or manage projects.",
25
+ content: PARALL_TASKS_SKILL,
26
+ },
27
+ {
28
+ name: "parall-wiki",
29
+ description: "Parall wiki operations: read, search, edit, and propose changes to organization knowledge bases. Use when: user asks to read/write docs, edit wiki pages, search knowledge base, propose changes, or review changesets.",
30
+ content: PARALL_WIKI_SKILL,
31
+ },
32
+ {
33
+ name: "parall-schedules",
34
+ description: "Parall schedule operations: create / pause / resume / cancel recurring or one-shot time triggers; respond to schedule fire events. Use when: user asks to set up a recurring reminder, schedule a delayed prompt, run cron-like work, or when the agent receives an `[Event: schedule.fired]` dispatch.",
35
+ content: PARALL_SCHEDULES_SKILL,
36
+ },
37
+ ];
38
+
39
+ /** Write plain skill markdown files to a target directory (CC/Codex). */
40
+ export function writeSkillFiles(targetDir: string): void {
41
+ fs.mkdirSync(targetDir, { recursive: true });
42
+ for (const skill of SKILLS) {
43
+ fs.writeFileSync(path.join(targetDir, `${skill.name}.md`), skill.content, "utf8");
44
+ }
45
+ }
46
+
47
+
48
+ export function buildSkillReferences(workspaceDir: string): string {
49
+ const dir = path.join(workspaceDir, ".parall", "skills");
50
+ const lines = SKILLS.map((s) => `- ${s.description.split(":")[0]}: \`${dir}/${s.name}.md\``);
51
+ return `## Platform Skills (read on demand)\n\n${lines.join("\n")}\n`;
52
+ }
@@ -0,0 +1,161 @@
1
+ export const PARALL_PLATFORM_SKILL = `# Parall Platform
2
+
3
+ Query organization data via the Parall CLI. Auth is pre-configured.
4
+
5
+ ## Identity
6
+
7
+ \`\`\`bash
8
+ parall whoami
9
+ \`\`\`
10
+
11
+ ## Members & Agents
12
+
13
+ \`\`\`bash
14
+ parall members list # All org members (humans + agents)
15
+ parall agents list # Agents only
16
+ parall users get prll://usr_xxx # Get user details by ID
17
+ \`\`\`
18
+
19
+ Create a hosted agent when the user asks for a Parall-managed runtime. Hosted
20
+ provisioning is asynchronous: creation means the agent identity, API key, and
21
+ machine record were accepted, not that the runtime is online yet. Use \`--wait\`
22
+ to wait until the machine reaches \`running\`, and use \`--wait-online\` when the
23
+ task requires the child agent to be connected before you report completion.
24
+ For hosted agents, use \`--discard-api-key\`; the server injects the one-time key
25
+ into the hosted runtime, so the parent agent must not print or persist it.
26
+
27
+ Create a self-hosted agent only when the runtime will be connected outside
28
+ Parall-managed compute. In that case, write the one-time \`api_key\` to
29
+ \`--api-key-file\` so it is not captured in tool-result logs. Treat \`api_key\` as a
30
+ secret: do not print, read aloud, post it in shared chats, or echo the file
31
+ contents. Include the \`user.id\` in normal responses, and pass the key file only
32
+ through an explicit secure runtime handoff when connection is required. Never
33
+ use \`--show-api-key\` from an agent runtime. Agent callers cannot set provider
34
+ overrides until the dedicated fine-grained permission flow lands.
35
+
36
+ \`\`\`bash
37
+ # Hosted runtime (Parall-managed compute)
38
+ parall agents create \\
39
+ --name "Research Agent" \\
40
+ --runtime-type codex \\
41
+ --machine-type cloud \\
42
+ --machine-label standard \\
43
+ --discard-api-key \\
44
+ --wait \\
45
+ --wait-online
46
+
47
+ # Self-hosted runtime
48
+ parall agents create --name "Research Agent" --runtime-type codex --api-key-file /tmp/research-agent.api-key
49
+ \`\`\`
50
+
51
+ Inspect hosted provisioning directly when a create command returns before the
52
+ runtime is online, or when you need logs for a failed machine. If \`agents create\`
53
+ exits non-zero after creating a hosted agent, read the printed \`user.id\` and
54
+ \`machine.id\`, then use these commands to decide whether to wait, inspect logs,
55
+ or report the failed machine for retry.
56
+
57
+ \`\`\`bash
58
+ parall machines status prll://mch_xxx
59
+ parall machines logs prll://mch_xxx --lines 100
60
+ \`\`\`
61
+
62
+ ## Chats & Messages
63
+
64
+ \`\`\`bash
65
+ parall chats list # List all chats
66
+ parall messages list prll://cht_xxx # Read chat message history
67
+ \`\`\`
68
+
69
+ ## Sending Messages
70
+
71
+ Each \`[Event: message.new]\` includes \`[Chat: ... (prll://cht_xxx)]\` — use that chat URI to reply.
72
+
73
+ \`\`\`bash
74
+ # Reply to a chat (use the chat URI from the event)
75
+ parall messages send prll://cht_xxx --text "Your reply"
76
+
77
+ # Direct message by user URI or display name
78
+ parall dm prll://usr_xxx --text "Hello"
79
+ parall dm "Alice" --text "Hello"
80
+
81
+ # Thread reply
82
+ parall messages send prll://cht_xxx --text "Reply" --thread-root-id 01JWC...
83
+
84
+ # FYI message (no response expected — the recipient sees \`[Hint: no_reply]\`)
85
+ parall messages send prll://cht_xxx --text "FYI: done" --no-reply
86
+
87
+ # Silence this turn entirely — no chat message produced. Use when you receive
88
+ # \`[Hint: no_reply]\` or otherwise decide the turn needs no visible reply.
89
+ # Run BEFORE any \`messages send\` / \`dm\`; those still deliver real messages.
90
+ parall no-reply --reason "ack only, nothing to add"
91
+ \`\`\`
92
+
93
+ ## Files & Attachments
94
+
95
+ Attachments appear in events as \`[Attachment: prll://att_xxx | mime | size | name]\`.
96
+
97
+ \`\`\`bash
98
+ # Download an attachment
99
+ parall files download att_xxx --output /tmp/file.png
100
+
101
+ # Upload a file (returns attachment_id)
102
+ parall files upload /tmp/report.pdf
103
+
104
+ # Send a message with a file
105
+ parall messages send prll://cht_xxx --file /tmp/output.png --text "Done"
106
+
107
+ # Send an existing attachment to another chat
108
+ parall messages send prll://cht_xxx --attachment att_xxx --text "See attached"
109
+
110
+ # DM with a file
111
+ parall dm "Alice" --file /tmp/report.pdf --text "Report attached"
112
+ \`\`\`
113
+
114
+ \`--file\` and \`--attachment\` are mutually exclusive. \`--text\` can be combined with either.
115
+
116
+ ## Approvals
117
+
118
+ When a CLI command returns a \`PERMISSION_DENIED\` error, the output includes a \`Tip:\` line with an approval-request template — copy it and fill in the remaining placeholders (\`--chat\`, \`--title\`, \`--reason\`). The \`Tip:\` only appears for commonly approvable actions (archive, delete, restore); for other actions, use \`parall approvals actions\` to check if it's approvable.
119
+
120
+ \`\`\`bash
121
+ # Request approval (use action and resource_uri from the error)
122
+ parall approvals request --action chat.archive --resource prll://cht_xxx --chat prll://cht_yyy --title "Archive old channel" --reason "No activity in 6 months"
123
+
124
+ # Check a specific approval's status
125
+ parall approvals get prll://apr_xxx
126
+
127
+ # Wait for a decision (blocks until approved/rejected/timeout)
128
+ parall approvals wait prll://apr_xxx --timeout 300
129
+
130
+ # List all your pending approvals
131
+ parall approvals list
132
+
133
+ # List available approvable actions
134
+ parall approvals actions
135
+
136
+ # Cancel a pending request you made
137
+ parall approvals cancel prll://apr_xxx
138
+ \`\`\`
139
+
140
+ Only request approval after receiving an actual \`PERMISSION_DENIED\` error — never preemptively. The \`--chat\` flag specifies where the approval card appears; use the chat where the conversation is happening.
141
+
142
+ ## Reference URIs
143
+
144
+ Every entity is addressable with a \`prll://\` URI. Common prefixes you'll see in events, messages, and schedule descriptions:
145
+
146
+ | Prefix | Entity | Skill |
147
+ |--------|--------|-------|
148
+ | \`prll://usr_\` | User (human or agent) | parall-platform |
149
+ | \`prll://cht_\` | Chat | parall-platform |
150
+ | \`prll://msg_\` | Message | parall-platform |
151
+ | \`prll://tsk_\` | Task | parall-tasks |
152
+ | \`prll://prj_\` | Project | parall-tasks |
153
+ | \`prll://sch_\` | Schedule (time trigger) | parall-schedules |
154
+ | \`prll://srn_\` | Schedule run (single fire audit record; carries fire-time snapshot) | parall-schedules |
155
+ | \`prll://wik_\` | Wiki | parall-wiki |
156
+ | \`prll://att_\` | Attachment | parall-platform (files) |
157
+
158
+ When a message or event references \`prll://sch_xxx\` or \`prll://srn_xxx\`, or when you receive \`[Event: schedule.fired]\`, switch to the **parall-schedules** skill for the CLI commands (create / list / pause / resume / cancel / runs).
159
+
160
+ All CLI output is JSON.
161
+ `;