@parall/agent-core 1.25.0 → 1.26.1
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/bridge-workspace.d.ts +1 -1
- package/dist/bridge-workspace.d.ts.map +1 -1
- package/dist/bridge-workspace.js +1 -1
- package/dist/dispatch-adapter.d.ts +5 -0
- package/dist/dispatch-adapter.d.ts.map +1 -1
- package/dist/event-format.d.ts +1 -0
- package/dist/event-format.d.ts.map +1 -1
- package/dist/event-format.js +6 -0
- package/dist/gateway-base.d.ts +6 -0
- package/dist/gateway-base.d.ts.map +1 -1
- package/dist/gateway-base.js +66 -11
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/platform-config.d.ts +23 -0
- package/dist/platform-config.d.ts.map +1 -0
- package/dist/platform-config.js +105 -0
- package/dist/skills/index.d.ts +14 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +44 -0
- package/dist/skills/parall-platform.d.ts +2 -0
- package/dist/skills/parall-platform.d.ts.map +1 -0
- package/dist/skills/parall-platform.js +161 -0
- package/dist/skills/parall-schedules.d.ts +2 -0
- package/dist/skills/parall-schedules.d.ts.map +1 -0
- package/dist/skills/parall-schedules.js +80 -0
- package/dist/skills/parall-tasks.d.ts +2 -0
- package/dist/skills/parall-tasks.d.ts.map +1 -0
- package/dist/skills/parall-tasks.js +65 -0
- package/dist/skills/parall-wiki.d.ts +2 -0
- package/dist/skills/parall-wiki.d.ts.map +1 -0
- package/dist/skills/parall-wiki.js +131 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/bridge-workspace.ts +1 -1
- package/src/dispatch-adapter.ts +6 -0
- package/src/event-format.ts +7 -0
- package/src/gateway-base.ts +66 -11
- package/src/index.ts +3 -0
- package/src/platform-config.ts +159 -0
- package/src/skills/index.ts +52 -0
- package/src/skills/parall-platform.ts +161 -0
- package/src/skills/parall-schedules.ts +80 -0
- package/src/skills/parall-tasks.ts +65 -0
- package/src/skills/parall-wiki.ts +131 -0
- package/src/types.ts +2 -0
package/src/gateway-base.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
TaskAssignedData,
|
|
18
18
|
TextContent,
|
|
19
19
|
} from "@parall/sdk";
|
|
20
|
-
import { buildEventBody, buildForkResultPrefix } from "./event-format.js";
|
|
20
|
+
import { buildEventBody, buildForkResultPrefix, buildForkScopePrefix } from "./event-format.js";
|
|
21
21
|
import type {
|
|
22
22
|
CleanupForkOpts,
|
|
23
23
|
DispatchAdapter,
|
|
@@ -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;
|
|
@@ -366,6 +368,7 @@ export class ParallAgentGateway {
|
|
|
366
368
|
chatId: event.type === "message" ? event.targetId : undefined,
|
|
367
369
|
triggerMessageId: event.messageId,
|
|
368
370
|
noReply: event.noReply ?? false,
|
|
371
|
+
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey),
|
|
369
372
|
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey),
|
|
370
373
|
client: this.opts.client,
|
|
371
374
|
log: this.opts.log,
|
|
@@ -401,7 +404,7 @@ export class ParallAgentGateway {
|
|
|
401
404
|
}
|
|
402
405
|
}
|
|
403
406
|
|
|
404
|
-
private async createRuntimeStep(event: ParallEvent, runtimeEvent: RuntimeEvent, stepIdFilePath?: string) {
|
|
407
|
+
private async createRuntimeStep(event: ParallEvent, runtimeEvent: RuntimeEvent, stepIdFilePath?: string, contextFilePath?: string) {
|
|
405
408
|
if (!this.activeSessionId) return;
|
|
406
409
|
|
|
407
410
|
const target = resolveStepTarget(event);
|
|
@@ -446,7 +449,9 @@ export class ParallAgentGateway {
|
|
|
446
449
|
group_key: runtimeEvent.groupKey,
|
|
447
450
|
runtime_key: runtimeEvent.callId,
|
|
448
451
|
});
|
|
449
|
-
if (
|
|
452
|
+
if (contextFilePath) {
|
|
453
|
+
this.updateContextFileStepId(contextFilePath, step.id);
|
|
454
|
+
} else if (stepIdFilePath) {
|
|
450
455
|
this.writeStepIdFile(stepIdFilePath, step.id);
|
|
451
456
|
}
|
|
452
457
|
break;
|
|
@@ -467,7 +472,9 @@ export class ParallAgentGateway {
|
|
|
467
472
|
},
|
|
468
473
|
group_key: runtimeEvent.groupKey,
|
|
469
474
|
});
|
|
470
|
-
if (
|
|
475
|
+
if (contextFilePath) {
|
|
476
|
+
this.updateContextFileStepId(contextFilePath, null);
|
|
477
|
+
} else if (stepIdFilePath) {
|
|
471
478
|
this.clearStepIdFile(stepIdFilePath);
|
|
472
479
|
}
|
|
473
480
|
break;
|
|
@@ -487,6 +494,27 @@ export class ParallAgentGateway {
|
|
|
487
494
|
}
|
|
488
495
|
}
|
|
489
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. */
|
|
490
518
|
private writeStepIdFile(filePath: string, stepId: string) {
|
|
491
519
|
try {
|
|
492
520
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
@@ -496,6 +524,7 @@ export class ParallAgentGateway {
|
|
|
496
524
|
}
|
|
497
525
|
}
|
|
498
526
|
|
|
527
|
+
/** @deprecated Use writeContextFile / updateContextFileStepId. */
|
|
499
528
|
private clearStepIdFile(filePath: string) {
|
|
500
529
|
try {
|
|
501
530
|
fs.writeFileSync(filePath, "", "utf8");
|
|
@@ -536,8 +565,19 @@ export class ParallAgentGateway {
|
|
|
536
565
|
setDispatchNoReply(sessionKey, event.noReply ?? false);
|
|
537
566
|
|
|
538
567
|
const dispatchContext = this.buildDispatchContext(event, sessionKey);
|
|
568
|
+
const contextFilePath = dispatchContext.contextFilePath;
|
|
539
569
|
const stepIdFilePath = dispatchContext.stepIdFilePath;
|
|
540
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
|
+
|
|
541
581
|
// sync: no await between the shuttingDown check above and this increment
|
|
542
582
|
// — JS event loop is single-threaded, so shutdown() cannot interleave
|
|
543
583
|
// here and miss our in-flight count.
|
|
@@ -567,19 +607,21 @@ export class ParallAgentGateway {
|
|
|
567
607
|
sessionKey,
|
|
568
608
|
context: dispatchContext,
|
|
569
609
|
})) {
|
|
570
|
-
await this.createRuntimeStep(event, runtimeEvent, stepIdFilePath);
|
|
610
|
+
await this.createRuntimeStep(event, runtimeEvent, stepIdFilePath, contextFilePath);
|
|
571
611
|
}
|
|
572
612
|
} catch (err) {
|
|
573
613
|
await this.createRuntimeStep(event, {
|
|
574
614
|
type: "error",
|
|
575
615
|
message: `Dispatch failed: ${String(err)}`,
|
|
576
|
-
}, stepIdFilePath);
|
|
616
|
+
}, stepIdFilePath, contextFilePath);
|
|
577
617
|
throw err;
|
|
578
618
|
} finally {
|
|
579
619
|
clearSessionMessageId(sessionKey);
|
|
580
620
|
clearDispatchMessageId(sessionKey);
|
|
581
621
|
clearDispatchNoReply(sessionKey);
|
|
582
|
-
if (
|
|
622
|
+
if (contextFilePath) {
|
|
623
|
+
this.updateContextFileStepId(contextFilePath, null);
|
|
624
|
+
} else if (stepIdFilePath) {
|
|
583
625
|
this.clearStepIdFile(stepIdFilePath);
|
|
584
626
|
}
|
|
585
627
|
if (this.activeSessionId) {
|
|
@@ -616,7 +658,7 @@ export class ParallAgentGateway {
|
|
|
616
658
|
const last = events[events.length - 1];
|
|
617
659
|
const earlier = events.slice(0, -1);
|
|
618
660
|
try {
|
|
619
|
-
const dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildEventBody(last), earlier);
|
|
661
|
+
const dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildForkScopePrefix(last) + buildEventBody(last), earlier);
|
|
620
662
|
if (!dispatched) {
|
|
621
663
|
// Shutdown short-circuit — resolve un-acked so the server requeues
|
|
622
664
|
// for the replacement pod and stop draining further items.
|
|
@@ -708,6 +750,8 @@ export class ParallAgentGateway {
|
|
|
708
750
|
body: "[Orchestrator: fork session(s) completed — review results above]",
|
|
709
751
|
};
|
|
710
752
|
this.dispatchState.mainCurrentTargetId = undefined;
|
|
753
|
+
this.dispatchState.mainPreDispatchBranchPoint =
|
|
754
|
+
this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
|
|
711
755
|
const dispatched = await this.runDispatch(syntheticEvent, this.opts.runtimeKey, forkPrefix + buildEventBody(syntheticEvent));
|
|
712
756
|
if (!dispatched) {
|
|
713
757
|
// Shutdown: put the fork results back at the head so the synthetic
|
|
@@ -730,8 +774,8 @@ export class ParallAgentGateway {
|
|
|
730
774
|
const pendingFork = this.dispatchState.pendingForkResults.splice(0);
|
|
731
775
|
const forkPrefix = buildForkResultPrefix(pendingFork);
|
|
732
776
|
this.dispatchState.mainCurrentTargetId = event.targetId;
|
|
733
|
-
|
|
734
|
-
|
|
777
|
+
this.dispatchState.mainPreDispatchBranchPoint =
|
|
778
|
+
this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
|
|
735
779
|
const dispatched = await this.runDispatch(event, this.opts.runtimeKey, forkPrefix + buildEventBody(event), earlier);
|
|
736
780
|
if (!dispatched) {
|
|
737
781
|
// Shutdown: skip the ack so the server redelivers these buffered
|
|
@@ -754,6 +798,7 @@ export class ParallAgentGateway {
|
|
|
754
798
|
this.draining = false;
|
|
755
799
|
this.dispatchState.mainDispatching = false;
|
|
756
800
|
this.dispatchState.mainCurrentTargetId = undefined;
|
|
801
|
+
this.dispatchState.mainPreDispatchBranchPoint = undefined;
|
|
757
802
|
}
|
|
758
803
|
}
|
|
759
804
|
|
|
@@ -766,6 +811,11 @@ export class ParallAgentGateway {
|
|
|
766
811
|
const forkPrefix = buildForkResultPrefix(pendingFork);
|
|
767
812
|
this.dispatchState.mainDispatching = true;
|
|
768
813
|
this.dispatchState.mainCurrentTargetId = event.targetId;
|
|
814
|
+
// Snapshot the on-disk branch point BEFORE runDispatch starts writing
|
|
815
|
+
// to the session file. Fork sessions created while main is in-flight
|
|
816
|
+
// use this to branch from the clean pre-dispatch state.
|
|
817
|
+
this.dispatchState.mainPreDispatchBranchPoint =
|
|
818
|
+
this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
|
|
769
819
|
let dispatched = false;
|
|
770
820
|
try {
|
|
771
821
|
dispatched = await this.runDispatch(event, this.opts.runtimeKey, forkPrefix + buildEventBody(event));
|
|
@@ -780,9 +830,13 @@ export class ParallAgentGateway {
|
|
|
780
830
|
return dispatched;
|
|
781
831
|
}
|
|
782
832
|
|
|
783
|
-
case "buffer-main":
|
|
833
|
+
case "buffer-main": {
|
|
834
|
+
if (this.shuttingDown) {
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
784
837
|
this.dispatchState.mainBuffer.push(event);
|
|
785
838
|
return false;
|
|
839
|
+
}
|
|
786
840
|
|
|
787
841
|
case "buffer-fork": {
|
|
788
842
|
const activeFork = this.forkStates.get(event.targetId);
|
|
@@ -804,6 +858,7 @@ export class ParallAgentGateway {
|
|
|
804
858
|
const fork = await this.opts.dispatchAdapter.forkSession({
|
|
805
859
|
sessionKey: this.opts.runtimeKey,
|
|
806
860
|
context: this.buildDispatchContext(event, this.opts.runtimeKey),
|
|
861
|
+
preDispatchBranchPoint: this.dispatchState.mainPreDispatchBranchPoint,
|
|
807
862
|
});
|
|
808
863
|
|
|
809
864
|
if (!fork) {
|
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
|
+
`;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export const PARALL_SCHEDULES_SKILL = `# Parall Schedules
|
|
2
|
+
|
|
3
|
+
A **Schedule** is a platform time trigger. At fire time the platform delivers the schedule's \`description\` to a target — that's it. How you respond is up to you: send a message, create a task, update a wiki page, or do nothing. Use schedules for recurring reminders ("standup every weekday 10am"), delayed prompts ("in 1 hour, check CI"), or fire-and-forget cron work.
|
|
4
|
+
|
|
5
|
+
Three spec types — pick exactly one:
|
|
6
|
+
|
|
7
|
+
- \`cron\` — 5-field expression (min granularity: 1 minute)
|
|
8
|
+
- \`interval\` — every N seconds (minimum 60)
|
|
9
|
+
- \`one_shot\` — fire once at a specific time
|
|
10
|
+
|
|
11
|
+
## Creating schedules
|
|
12
|
+
|
|
13
|
+
\`\`\`bash
|
|
14
|
+
# Recurring cron (weekdays 10am New York)
|
|
15
|
+
parall schedules create \\
|
|
16
|
+
--name "Daily standup" \\
|
|
17
|
+
--description "Ask the team for their plan today; see prll://wik_xxx for the standup template" \\
|
|
18
|
+
--target-ids prll://usr_xxx \\
|
|
19
|
+
--cron-expr "0 10 * * 1-5" \\
|
|
20
|
+
--timezone America/New_York \\
|
|
21
|
+
--attached-to-uri prll://cht_xxx
|
|
22
|
+
|
|
23
|
+
# Every 30 minutes
|
|
24
|
+
parall schedules create \\
|
|
25
|
+
--name "CI watch" \\
|
|
26
|
+
--description "Check the deploy status and flag failures" \\
|
|
27
|
+
--target-ids prll://usr_xxx \\
|
|
28
|
+
--interval-seconds 1800
|
|
29
|
+
|
|
30
|
+
# One-shot at a future RFC3339 time
|
|
31
|
+
parall schedules create \\
|
|
32
|
+
--name "Followup" \\
|
|
33
|
+
--description "Remind the user about the PR review if still pending" \\
|
|
34
|
+
--target-ids prll://usr_xxx \\
|
|
35
|
+
--run-at <FUTURE_RFC3339_TIME>
|
|
36
|
+
\`\`\`
|
|
37
|
+
|
|
38
|
+
\`--target-ids\` is who receives the fire (usually yourself when you're self-scheduling; another agent or human when delegating). \`--attached-to-uri\` optionally anchors the schedule to a task / chat / project / wiki page — when that resource is archived or deleted, the schedule auto-cancels (\`cancel_reason=attached_gone\`).
|
|
39
|
+
|
|
40
|
+
## Listing / inspecting
|
|
41
|
+
|
|
42
|
+
\`\`\`bash
|
|
43
|
+
parall schedules list --status active,paused
|
|
44
|
+
parall schedules list --attached-to prll://tsk_xxx
|
|
45
|
+
parall schedules list --attendee-id prll://usr_xxx
|
|
46
|
+
parall schedules get prll://sch_xxx
|
|
47
|
+
parall schedules runs prll://sch_xxx # fire history
|
|
48
|
+
parall schedules run prll://srn_xxx # single run incl. fire-time snapshot
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
## Lifecycle
|
|
52
|
+
|
|
53
|
+
\`\`\`bash
|
|
54
|
+
parall schedules update prll://sch_xxx --description "New prompt"
|
|
55
|
+
parall schedules pause prll://sch_xxx # reversible
|
|
56
|
+
parall schedules resume prll://sch_xxx # does NOT catch up missed slots
|
|
57
|
+
parall schedules cancel prll://sch_xxx # terminal; row + runs preserved, prll://sch_ ref stays valid
|
|
58
|
+
parall schedules delete prll://sch_xxx # hard-delete; requires status=cancelled AND run_count=0. Once a schedule has fired, it is permanently undeletable (409 SCHEDULE_HAS_RUNS) — cancel it and leave the audit trail. Delete is for never-fired test/accidental schedules only.
|
|
59
|
+
\`\`\`
|
|
60
|
+
|
|
61
|
+
\`spec_type\` cannot be changed via update — if you need to switch between cron / interval / one_shot, cancel the old one and create a new schedule.
|
|
62
|
+
|
|
63
|
+
## Responding to schedule fires
|
|
64
|
+
|
|
65
|
+
When you receive \`[Event: schedule.fired]\`, the platform has fired a schedule targeting you.
|
|
66
|
+
|
|
67
|
+
The runtime (agent-core) has already done the heavy lifting: it fetched the schedule run and inlined the fire-time \`description\` (a frozen snapshot — later edits to the schedule don't change past fires) into your prompt, alongside \`[Schedule: prll://sch_xxx]\` and \`[Run: prll://srn_xxx]\` headers. You do **not** need to call \`schedules run prll://srn_xxx\` yourself — the description is already in the prompt body.
|
|
68
|
+
|
|
69
|
+
Your job is to interpret the description and act:
|
|
70
|
+
|
|
71
|
+
1. Read the description and any \`prll://\` refs it contains
|
|
72
|
+
2. Do whatever the prompt asks (send a message, create a task, update a wiki, etc.) — there is no canonical response format
|
|
73
|
+
3. Optional: if the fire is genuinely a no-op and you don't want to produce any artifact, use \`no-reply\` (from parall-platform skill) to stay silent for this turn
|
|
74
|
+
|
|
75
|
+
Do not treat schedule fires as "tasks assigned to you" — there's no status to transition, no acknowledgment required. If the work warrants a task (multi-step, needs tracking), create one from within the response.
|
|
76
|
+
|
|
77
|
+
**Fetching the run explicitly** (optional): \`schedules run prll://srn_xxx\` returns the same snapshot plus delivery records (reverse-lookable via \`source_id=srn_xxx\`) for audit. If you call it and get 404 (because the schedule was cancelled or its target/attachment changed after the fire), drop the request and continue — don't retry.
|
|
78
|
+
|
|
79
|
+
CLI command results are JSON on stdout; mutation commands may emit auxiliary hints on stderr (for example, \`Created: prll://sch_xxx\`).
|
|
80
|
+
`;
|