@parall/parall 1.26.1 → 1.26.3
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/accounts.d.ts +9 -0
- package/dist/accounts.d.ts.map +1 -0
- package/dist/accounts.js +29 -0
- package/dist/channel.d.ts +4 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +36 -0
- package/dist/config-manager.d.ts +22 -0
- package/dist/config-manager.d.ts.map +1 -0
- package/dist/config-manager.js +154 -0
- package/dist/fork.d.ts +30 -0
- package/dist/fork.d.ts.map +1 -0
- package/dist/fork.js +162 -0
- package/dist/gateway.d.ts +7 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +330 -0
- package/dist/hooks.d.ts +3 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +194 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/outbound.d.ts +6 -0
- package/dist/outbound.d.ts.map +1 -0
- package/dist/outbound.js +30 -0
- package/dist/routing.d.ts +3 -0
- package/dist/routing.d.ts.map +1 -0
- package/dist/routing.js +1 -0
- package/dist/runtime.d.ts +26 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +32 -0
- package/dist/session.d.ts +15 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +36 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/wiki-helper.d.ts +21 -0
- package/dist/wiki-helper.d.ts.map +1 -0
- package/dist/wiki-helper.js +131 -0
- package/openclaw.plugin.json +17 -0
- package/package.json +7 -6
- package/src/config-manager.ts +16 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ResolvedParallAccount } from "./types.js";
|
|
2
|
+
type OpenClawConfig = Record<string, unknown>;
|
|
3
|
+
export declare function listParallAccountIds(cfg: OpenClawConfig): string[];
|
|
4
|
+
export declare function resolveParallAccount(params: {
|
|
5
|
+
cfg: OpenClawConfig;
|
|
6
|
+
accountId?: string;
|
|
7
|
+
}): ResolvedParallAccount;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=accounts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accounts.d.ts","sourceRoot":"","sources":["../src/accounts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAuB,MAAM,YAAY,CAAC;AAI7E,KAAK,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAO9C,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,EAAE,CAIlE;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE;IAC3C,GAAG,EAAE,cAAc,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,qBAAqB,CAiBxB"}
|
package/dist/accounts.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const DEFAULT_ACCOUNT_ID = "default";
|
|
2
|
+
function readParallConfig(cfg) {
|
|
3
|
+
const channels = cfg.channels;
|
|
4
|
+
return channels?.parall;
|
|
5
|
+
}
|
|
6
|
+
export function listParallAccountIds(cfg) {
|
|
7
|
+
const parallCfg = readParallConfig(cfg);
|
|
8
|
+
if (!parallCfg)
|
|
9
|
+
return [];
|
|
10
|
+
return [DEFAULT_ACCOUNT_ID];
|
|
11
|
+
}
|
|
12
|
+
export function resolveParallAccount(params) {
|
|
13
|
+
const { cfg, accountId = DEFAULT_ACCOUNT_ID } = params;
|
|
14
|
+
const parallCfg = readParallConfig(cfg);
|
|
15
|
+
const config = {
|
|
16
|
+
parall_url: parallCfg?.parall_url ?? "",
|
|
17
|
+
api_key: parallCfg?.api_key ?? "",
|
|
18
|
+
org_id: parallCfg?.org_id ?? "",
|
|
19
|
+
ws_url: parallCfg?.ws_url,
|
|
20
|
+
enabled: parallCfg?.enabled,
|
|
21
|
+
};
|
|
22
|
+
const configured = Boolean(config.api_key && config.parall_url && config.org_id);
|
|
23
|
+
return {
|
|
24
|
+
accountId,
|
|
25
|
+
enabled: config.enabled !== false,
|
|
26
|
+
configured,
|
|
27
|
+
config,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAI9D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAWxD,eAAO,MAAM,YAAY,EAAE,aAAa,CAAC,qBAAqB,CAwB7D,CAAC"}
|
package/dist/channel.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { listParallAccountIds, resolveParallAccount } from "./accounts.js";
|
|
2
|
+
import { parallGateway } from "./gateway.js";
|
|
3
|
+
import { parallOutbound } from "./outbound.js";
|
|
4
|
+
const meta = {
|
|
5
|
+
id: "parall",
|
|
6
|
+
label: "Parall",
|
|
7
|
+
selectionLabel: "Parall IM",
|
|
8
|
+
docsPath: "/channels/parall",
|
|
9
|
+
blurb: "Agent-Native IM platform.",
|
|
10
|
+
order: 80,
|
|
11
|
+
};
|
|
12
|
+
export const parallPlugin = {
|
|
13
|
+
id: "parall",
|
|
14
|
+
meta,
|
|
15
|
+
capabilities: {
|
|
16
|
+
chatTypes: ["direct", "group"],
|
|
17
|
+
polls: false,
|
|
18
|
+
threads: false,
|
|
19
|
+
media: true,
|
|
20
|
+
reactions: false,
|
|
21
|
+
edit: false,
|
|
22
|
+
reply: false,
|
|
23
|
+
},
|
|
24
|
+
config: {
|
|
25
|
+
listAccountIds: (cfg) => listParallAccountIds(cfg),
|
|
26
|
+
resolveAccount: (cfg, accountId) => resolveParallAccount({ cfg, accountId: accountId ?? undefined }),
|
|
27
|
+
isConfigured: (account) => account.configured,
|
|
28
|
+
describeAccount: (account) => ({
|
|
29
|
+
accountId: account.accountId,
|
|
30
|
+
enabled: account.enabled,
|
|
31
|
+
configured: account.configured,
|
|
32
|
+
}),
|
|
33
|
+
},
|
|
34
|
+
outbound: parallOutbound,
|
|
35
|
+
gateway: parallGateway,
|
|
36
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ParallClient } from "@parall/sdk";
|
|
2
|
+
interface ConfigManagerOpts {
|
|
3
|
+
client: ParallClient;
|
|
4
|
+
stateDir: string;
|
|
5
|
+
configPath: string;
|
|
6
|
+
credentials: {
|
|
7
|
+
api_key: string;
|
|
8
|
+
parall_url: string;
|
|
9
|
+
};
|
|
10
|
+
log?: {
|
|
11
|
+
info: (msg: string) => void;
|
|
12
|
+
warn: (msg: string) => void;
|
|
13
|
+
error: (msg: string) => void;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Fetch platform config from the server, cache it (LKG), and apply to openclaw.json.
|
|
18
|
+
* Degrades gracefully: uses cached config on fetch failure, continues if neither is available.
|
|
19
|
+
*/
|
|
20
|
+
export declare function fetchAndApplyPlatformConfig(opts: ConfigManagerOpts): Promise<void>;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=config-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-manager.d.ts","sourceRoot":"","sources":["../src/config-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAWhD,UAAU,iBAAiB;IACzB,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,GAAG,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CAClG;AA+HD;;;GAGG;AACH,wBAAsB,2BAA2B,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6CxF"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
const CACHE_FILENAME = "parall-platform-config.json";
|
|
4
|
+
/** Tool names are no longer registered — all operations go through CLI. */
|
|
5
|
+
function cachePath(stateDir) {
|
|
6
|
+
return path.join(stateDir, CACHE_FILENAME);
|
|
7
|
+
}
|
|
8
|
+
function loadCachedConfig(stateDir) {
|
|
9
|
+
try {
|
|
10
|
+
const raw = fs.readFileSync(cachePath(stateDir), "utf-8");
|
|
11
|
+
return JSON.parse(raw);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function saveCachedConfig(stateDir, config) {
|
|
18
|
+
const cached = {
|
|
19
|
+
version: config.version,
|
|
20
|
+
config: config.config,
|
|
21
|
+
fetchedAt: new Date().toISOString(),
|
|
22
|
+
};
|
|
23
|
+
const filePath = cachePath(stateDir);
|
|
24
|
+
const tmpPath = `${filePath}.tmp`;
|
|
25
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
26
|
+
fs.writeFileSync(tmpPath, JSON.stringify(cached, null, 2), "utf-8");
|
|
27
|
+
fs.renameSync(tmpPath, filePath);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Deep-merge platform config into an existing openclaw.json file.
|
|
31
|
+
* Only overwrites `models.providers.parall` and `agents.defaults` sections;
|
|
32
|
+
* all other keys (user customizations, other providers) are preserved.
|
|
33
|
+
*/
|
|
34
|
+
function applyToOpenClawConfig(configPath, platformConfig, credentials) {
|
|
35
|
+
let existing = {};
|
|
36
|
+
try {
|
|
37
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
38
|
+
existing = JSON.parse(raw);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// File doesn't exist or is invalid — start fresh
|
|
42
|
+
}
|
|
43
|
+
// Deep-merge models.providers.parall (preserve existing fields, overlay platform, inject credentials)
|
|
44
|
+
const models = (existing.models ?? {});
|
|
45
|
+
const providers = (models.providers ?? {});
|
|
46
|
+
const existingParall = (providers.parall ?? {});
|
|
47
|
+
const platformModels = (platformConfig.models ?? {});
|
|
48
|
+
const platformProviders = (platformModels.providers ?? {});
|
|
49
|
+
const platformParall = (platformProviders.parall ?? {});
|
|
50
|
+
// Only forward keys OpenClaw's model schema recognizes — its strict Zod
|
|
51
|
+
// validation rejects unrecognized keys and skips the entire config reload.
|
|
52
|
+
const OPENCLAW_MODEL_KEYS = new Set(["id", "name", "contextWindow", "maxTokens"]);
|
|
53
|
+
if (Array.isArray(platformParall.models)) {
|
|
54
|
+
platformParall.models = platformParall.models.map((m) => {
|
|
55
|
+
const cleaned = {};
|
|
56
|
+
for (const [k, v] of Object.entries(m)) {
|
|
57
|
+
if (OPENCLAW_MODEL_KEYS.has(k)) {
|
|
58
|
+
cleaned[k] = v;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return cleaned;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
providers.parall = {
|
|
65
|
+
...existingParall,
|
|
66
|
+
...platformParall,
|
|
67
|
+
apiKey: credentials.api_key,
|
|
68
|
+
};
|
|
69
|
+
models.providers = providers;
|
|
70
|
+
existing.models = models;
|
|
71
|
+
// Strip keys OpenClaw doesn't recognize from agents.defaults — platform-only
|
|
72
|
+
// keys like thinking_effort are consumed by agent-core's PlatformConfigManager.
|
|
73
|
+
// See: OpenClaw agents.defaults config schema.
|
|
74
|
+
const OPENCLAW_AGENTS_DEFAULTS_KEYS = new Set(["model", "compaction"]);
|
|
75
|
+
// Unconditionally sanitize existing defaults to self-heal already-poisoned
|
|
76
|
+
// openclaw.json files (e.g. mode=self agents that were poisoned while in
|
|
77
|
+
// platform mode).
|
|
78
|
+
const agents = (existing.agents ?? {});
|
|
79
|
+
const existingDefaults = (agents.defaults ?? {});
|
|
80
|
+
const cleanedExisting = {};
|
|
81
|
+
for (const [k, v] of Object.entries(existingDefaults)) {
|
|
82
|
+
if (OPENCLAW_AGENTS_DEFAULTS_KEYS.has(k)) {
|
|
83
|
+
cleanedExisting[k] = v;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Overlay platform defaults (filtered) when present.
|
|
87
|
+
const platformAgents = (platformConfig.agents ?? {});
|
|
88
|
+
const rawDefaults = platformAgents.defaults;
|
|
89
|
+
if (rawDefaults !== undefined &&
|
|
90
|
+
rawDefaults !== null &&
|
|
91
|
+
typeof rawDefaults === "object" &&
|
|
92
|
+
!Array.isArray(rawDefaults)) {
|
|
93
|
+
const raw = rawDefaults;
|
|
94
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
95
|
+
if (OPENCLAW_AGENTS_DEFAULTS_KEYS.has(k)) {
|
|
96
|
+
cleanedExisting[k] = v;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
agents.defaults = cleanedExisting;
|
|
101
|
+
existing.agents = agents;
|
|
102
|
+
// No plugin tools to allow — all operations go through CLI.
|
|
103
|
+
// Atomic write
|
|
104
|
+
const tmpPath = `${configPath}.tmp`;
|
|
105
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
106
|
+
fs.writeFileSync(tmpPath, JSON.stringify(existing, null, 2), "utf-8");
|
|
107
|
+
fs.renameSync(tmpPath, configPath);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Fetch platform config from the server, cache it (LKG), and apply to openclaw.json.
|
|
111
|
+
* Degrades gracefully: uses cached config on fetch failure, continues if neither is available.
|
|
112
|
+
*/
|
|
113
|
+
export async function fetchAndApplyPlatformConfig(opts) {
|
|
114
|
+
const { client, stateDir, configPath, credentials, log } = opts;
|
|
115
|
+
// 1. Load cached config (LKG)
|
|
116
|
+
const cached = loadCachedConfig(stateDir);
|
|
117
|
+
// 2. Fetch from server (with version for 304)
|
|
118
|
+
let fresh = null;
|
|
119
|
+
try {
|
|
120
|
+
fresh = await client.getPlatformConfig(cached?.version);
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
// Fetch failed — fall through to use cache
|
|
124
|
+
if (cached) {
|
|
125
|
+
log?.warn(`platform config fetch failed, using cached version ${cached.version}: ${String(err)}`);
|
|
126
|
+
applyToOpenClawConfig(configPath, cached.config, credentials);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// No cache and fetch fails — degrade gracefully
|
|
130
|
+
log?.error(`platform config fetch failed and no cache available: ${String(err)}`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// 3. 304 — config unchanged
|
|
134
|
+
if (fresh === null) {
|
|
135
|
+
log?.info("platform config unchanged (304)");
|
|
136
|
+
if (cached) {
|
|
137
|
+
applyToOpenClawConfig(configPath, cached.config, credentials);
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// 4. Validate schema compatibility before accepting
|
|
142
|
+
const SUPPORTED_SCHEMA_VERSION = 1;
|
|
143
|
+
if (fresh.schema_version !== undefined && fresh.schema_version > SUPPORTED_SCHEMA_VERSION) {
|
|
144
|
+
log?.error(`platform config schema_version ${fresh.schema_version} is newer than supported (${SUPPORTED_SCHEMA_VERSION}), keeping current config`);
|
|
145
|
+
if (cached) {
|
|
146
|
+
applyToOpenClawConfig(configPath, cached.config, credentials);
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// 5. Newer config received — save cache and apply
|
|
151
|
+
log?.info(`platform config updated to version ${fresh.version}`);
|
|
152
|
+
saveCachedConfig(stateDir, fresh);
|
|
153
|
+
applyToOpenClawConfig(configPath, fresh.config, credentials);
|
|
154
|
+
}
|
package/dist/fork.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type ForkSessionResult = {
|
|
2
|
+
sessionKey: string;
|
|
3
|
+
sessionId: string;
|
|
4
|
+
sessionFile: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function resolveTranscriptFile(sessionsDir: string, sessionKey: string): string | null;
|
|
7
|
+
/**
|
|
8
|
+
* Fork the orchestrator session's transcript into a new parallel session.
|
|
9
|
+
*
|
|
10
|
+
* The fork inherits all history up to the current dispatch start (on-disk state).
|
|
11
|
+
* It gets its own session key, write lock, and can dispatch independently.
|
|
12
|
+
*
|
|
13
|
+
* Creates a session store entry with lineage metadata (spawnedBy).
|
|
14
|
+
*/
|
|
15
|
+
export declare function forkOrchestratorSession(opts: {
|
|
16
|
+
orchestratorSessionKey: string;
|
|
17
|
+
accountId: string;
|
|
18
|
+
transcriptFile: string;
|
|
19
|
+
sessionsDir: string;
|
|
20
|
+
branchPointId?: string;
|
|
21
|
+
}): ForkSessionResult | null;
|
|
22
|
+
/**
|
|
23
|
+
* Clean up a fork session: delete transcript file AND session store entry.
|
|
24
|
+
*/
|
|
25
|
+
export declare function cleanupForkSession(opts: {
|
|
26
|
+
sessionFile: string;
|
|
27
|
+
sessionKey: string;
|
|
28
|
+
sessionsDir: string;
|
|
29
|
+
}): void;
|
|
30
|
+
//# sourceMappingURL=fork.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fork.d.ts","sourceRoot":"","sources":["../src/fork.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AA+CF,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqB5F;AAMD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,sBAAsB,EAAE,MAAM,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,iBAAiB,GAAG,IAAI,CA+D3B;AAMD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,IAAI,CASP"}
|
package/dist/fork.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// fork.ts — Fork-on-busy engine for orchestrator mode
|
|
2
|
+
//
|
|
3
|
+
// When the orchestrator's main session is busy, new events are handled by
|
|
4
|
+
// forking the main session's transcript and dispatching to the fork in parallel.
|
|
5
|
+
// The caller provides a pre-dispatch branch point so the fork inherits
|
|
6
|
+
// history up to (but not including) the in-flight dispatch. Without a
|
|
7
|
+
// branch point, the fork falls back to the current on-disk leaf — which
|
|
8
|
+
// may include partial in-flight state.
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import * as crypto from "node:crypto";
|
|
12
|
+
import { CURRENT_SESSION_VERSION, SessionManager } from "@mariozechner/pi-coding-agent";
|
|
13
|
+
function readStoreEntry(sessionsDir, sessionKey) {
|
|
14
|
+
const storeFile = path.join(sessionsDir, "sessions.json");
|
|
15
|
+
try {
|
|
16
|
+
const store = JSON.parse(fs.readFileSync(storeFile, "utf-8"));
|
|
17
|
+
return store[sessionKey] ?? store[sessionKey.toLowerCase()] ?? null;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function writeStoreEntry(sessionsDir, sessionKey, entry) {
|
|
24
|
+
const storeFile = path.join(sessionsDir, "sessions.json");
|
|
25
|
+
try {
|
|
26
|
+
let store = {};
|
|
27
|
+
try {
|
|
28
|
+
store = JSON.parse(fs.readFileSync(storeFile, "utf-8"));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Empty or missing — start fresh.
|
|
32
|
+
}
|
|
33
|
+
store[sessionKey.toLowerCase()] = entry;
|
|
34
|
+
fs.writeFileSync(storeFile, JSON.stringify(store, null, 2), { encoding: "utf-8" });
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function deleteStoreEntry(sessionsDir, sessionKey) {
|
|
42
|
+
const storeFile = path.join(sessionsDir, "sessions.json");
|
|
43
|
+
try {
|
|
44
|
+
const store = JSON.parse(fs.readFileSync(storeFile, "utf-8"));
|
|
45
|
+
delete store[sessionKey];
|
|
46
|
+
delete store[sessionKey.toLowerCase()];
|
|
47
|
+
fs.writeFileSync(storeFile, JSON.stringify(store, null, 2), { encoding: "utf-8" });
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Best-effort cleanup.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function resolveTranscriptFile(sessionsDir, sessionKey) {
|
|
54
|
+
const entry = readStoreEntry(sessionsDir, sessionKey);
|
|
55
|
+
if (!entry?.sessionId)
|
|
56
|
+
return null;
|
|
57
|
+
if (entry.sessionFile) {
|
|
58
|
+
const resolved = path.isAbsolute(entry.sessionFile)
|
|
59
|
+
? entry.sessionFile
|
|
60
|
+
: path.join(sessionsDir, entry.sessionFile);
|
|
61
|
+
if (fs.existsSync(resolved))
|
|
62
|
+
return resolved;
|
|
63
|
+
}
|
|
64
|
+
const conventional = path.join(sessionsDir, `${entry.sessionId}.jsonl`);
|
|
65
|
+
if (fs.existsSync(conventional))
|
|
66
|
+
return conventional;
|
|
67
|
+
try {
|
|
68
|
+
const files = fs.readdirSync(sessionsDir);
|
|
69
|
+
const match = files.find((file) => file.includes(entry.sessionId) && file.endsWith(".jsonl"));
|
|
70
|
+
return match ? path.join(sessionsDir, match) : null;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Fork creation
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
/**
|
|
80
|
+
* Fork the orchestrator session's transcript into a new parallel session.
|
|
81
|
+
*
|
|
82
|
+
* The fork inherits all history up to the current dispatch start (on-disk state).
|
|
83
|
+
* It gets its own session key, write lock, and can dispatch independently.
|
|
84
|
+
*
|
|
85
|
+
* Creates a session store entry with lineage metadata (spawnedBy).
|
|
86
|
+
*/
|
|
87
|
+
export function forkOrchestratorSession(opts) {
|
|
88
|
+
const { orchestratorSessionKey, accountId, transcriptFile, sessionsDir } = opts;
|
|
89
|
+
if (!fs.existsSync(transcriptFile))
|
|
90
|
+
return null;
|
|
91
|
+
try {
|
|
92
|
+
const manager = SessionManager.open(transcriptFile);
|
|
93
|
+
const leafId = opts.branchPointId ?? manager.getLeafId();
|
|
94
|
+
let sessionId;
|
|
95
|
+
let sessionFile;
|
|
96
|
+
if (leafId) {
|
|
97
|
+
// Branch from the current leaf — inherits full transcript history
|
|
98
|
+
const branched = manager.createBranchedSession(leafId) ?? manager.getSessionFile();
|
|
99
|
+
const branchedId = manager.getSessionId();
|
|
100
|
+
if (branched && branchedId) {
|
|
101
|
+
sessionFile = branched;
|
|
102
|
+
sessionId = branchedId;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// Fallback: no leaf node — create a header-only fork file manually.
|
|
110
|
+
sessionId = crypto.randomUUID();
|
|
111
|
+
const timestamp = new Date().toISOString();
|
|
112
|
+
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
113
|
+
sessionFile = path.join(manager.getSessionDir(), `${fileTimestamp}_${sessionId}.jsonl`);
|
|
114
|
+
const header = {
|
|
115
|
+
type: "session",
|
|
116
|
+
version: CURRENT_SESSION_VERSION,
|
|
117
|
+
id: sessionId,
|
|
118
|
+
timestamp,
|
|
119
|
+
cwd: manager.getCwd(),
|
|
120
|
+
parentSession: transcriptFile,
|
|
121
|
+
};
|
|
122
|
+
fs.writeFileSync(sessionFile, `${JSON.stringify(header)}\n`, {
|
|
123
|
+
encoding: "utf-8",
|
|
124
|
+
mode: 0o600,
|
|
125
|
+
flag: "wx",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const forkSessionKey = `${orchestratorSessionKey}:fork:${sessionId}`;
|
|
129
|
+
// Register in session store — fail closed if store is unwritable
|
|
130
|
+
const wrote = writeStoreEntry(sessionsDir, forkSessionKey, {
|
|
131
|
+
sessionId,
|
|
132
|
+
sessionFile: path.relative(sessionsDir, sessionFile),
|
|
133
|
+
updatedAt: Date.now(),
|
|
134
|
+
spawnedBy: orchestratorSessionKey,
|
|
135
|
+
parentSessionKey: orchestratorSessionKey,
|
|
136
|
+
forkedFromParent: true,
|
|
137
|
+
});
|
|
138
|
+
if (!wrote)
|
|
139
|
+
return null;
|
|
140
|
+
return { sessionKey: forkSessionKey, sessionId, sessionFile };
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// Fork cleanup (store-driven)
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
/**
|
|
150
|
+
* Clean up a fork session: delete transcript file AND session store entry.
|
|
151
|
+
*/
|
|
152
|
+
export function cleanupForkSession(opts) {
|
|
153
|
+
try {
|
|
154
|
+
if (fs.existsSync(opts.sessionFile)) {
|
|
155
|
+
fs.unlinkSync(opts.sessionFile);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Best-effort file cleanup
|
|
160
|
+
}
|
|
161
|
+
deleteStoreEntry(opts.sessionsDir, opts.sessionKey);
|
|
162
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
|
|
2
|
+
/** Extract ChannelGatewayAdapter from ChannelPlugin (removed from public SDK exports in 2026.3.24). */
|
|
3
|
+
type ChannelGatewayAdapter<T = unknown> = NonNullable<ChannelPlugin<T>["gateway"]>;
|
|
4
|
+
import type { ResolvedParallAccount } from "./types.js";
|
|
5
|
+
export declare const parallGateway: ChannelGatewayAdapter<ResolvedParallAccount>;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=gateway.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,uGAAuG;AACvG,KAAK,qBAAqB,CAAC,CAAC,GAAG,OAAO,IAAI,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AAsBnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AA6NxD,eAAO,MAAM,aAAa,EAAE,qBAAqB,CAAC,qBAAqB,CA2HtE,CAAC"}
|