@parall/parall 1.31.0 → 1.32.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/accounts.d.ts +1 -1
- package/dist/accounts.js +4 -4
- package/dist/channel.d.ts +2 -2
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +10 -10
- package/dist/config-manager.d.ts +1 -1
- package/dist/config-manager.d.ts.map +1 -1
- package/dist/config-manager.js +70 -30
- package/dist/fork.d.ts.map +1 -1
- package/dist/fork.js +17 -17
- package/dist/gateway.d.ts +3 -3
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +147 -127
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +52 -26
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -7
- package/dist/oc-session.d.ts.map +1 -1
- package/dist/oc-session.js +35 -27
- package/dist/outbound.d.ts +2 -2
- package/dist/outbound.d.ts.map +1 -1
- package/dist/outbound.js +13 -14
- package/dist/routing.d.ts +2 -2
- package/dist/routing.js +1 -1
- package/dist/runtime.d.ts +5 -5
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +2 -2
- package/dist/session.js +4 -4
- package/dist/wiki-helper.d.ts.map +1 -1
- package/dist/wiki-helper.js +27 -27
- package/openclaw.plugin.json +4 -1
- package/package.json +3 -3
- package/skills/parall-clips/SKILL.md +48 -0
- package/src/accounts.ts +5 -5
- package/src/channel.ts +15 -14
- package/src/config-manager.ts +79 -34
- package/src/fork.ts +26 -22
- package/src/gateway.ts +177 -134
- package/src/hooks.ts +109 -50
- package/src/index.ts +8 -8
- package/src/oc-session.ts +43 -35
- package/src/outbound.ts +18 -17
- package/src/routing.ts +2 -2
- package/src/runtime.ts +11 -7
- package/src/session.ts +4 -4
- package/src/wiki-helper.ts +47 -34
package/src/config-manager.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { ParallClient } from
|
|
2
|
-
import type { PlatformConfigResponse } from
|
|
3
|
-
import * as fs from
|
|
4
|
-
import * as path from
|
|
1
|
+
import type { ParallClient } from '@parall/sdk';
|
|
2
|
+
import type { PlatformConfigResponse } from '@parall/sdk';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
5
|
|
|
6
6
|
interface CachedPlatformConfig {
|
|
7
7
|
version: string;
|
|
@@ -17,7 +17,7 @@ interface ConfigManagerOpts {
|
|
|
17
17
|
log?: { info: (msg: string) => void; warn: (msg: string) => void; error: (msg: string) => void };
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const CACHE_FILENAME =
|
|
20
|
+
const CACHE_FILENAME = 'parall-platform-config.json';
|
|
21
21
|
|
|
22
22
|
/** Tool names are no longer registered — all operations go through CLI. */
|
|
23
23
|
|
|
@@ -27,7 +27,7 @@ function cachePath(stateDir: string): string {
|
|
|
27
27
|
|
|
28
28
|
function loadCachedConfig(stateDir: string): CachedPlatformConfig | null {
|
|
29
29
|
try {
|
|
30
|
-
const raw = fs.readFileSync(cachePath(stateDir),
|
|
30
|
+
const raw = fs.readFileSync(cachePath(stateDir), 'utf-8');
|
|
31
31
|
return JSON.parse(raw) as CachedPlatformConfig;
|
|
32
32
|
} catch {
|
|
33
33
|
return null;
|
|
@@ -43,13 +43,14 @@ function saveCachedConfig(stateDir: string, config: PlatformConfigResponse): voi
|
|
|
43
43
|
const filePath = cachePath(stateDir);
|
|
44
44
|
const tmpPath = `${filePath}.tmp`;
|
|
45
45
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
46
|
-
fs.writeFileSync(tmpPath, JSON.stringify(cached, null, 2),
|
|
46
|
+
fs.writeFileSync(tmpPath, JSON.stringify(cached, null, 2), 'utf-8');
|
|
47
47
|
fs.renameSync(tmpPath, filePath);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* Deep-merge platform config into an existing openclaw.json file.
|
|
52
|
-
* Only overwrites `models.providers.parall
|
|
52
|
+
* Only overwrites `models.providers.parall`, `models.providers["parall-anthropic"]`,
|
|
53
|
+
* and `agents.defaults` sections;
|
|
53
54
|
* all other keys (user customizations, other providers) are preserved.
|
|
54
55
|
*/
|
|
55
56
|
function applyToOpenClawConfig(
|
|
@@ -59,47 +60,74 @@ function applyToOpenClawConfig(
|
|
|
59
60
|
): void {
|
|
60
61
|
let existing: Record<string, unknown> = {};
|
|
61
62
|
try {
|
|
62
|
-
const raw = fs.readFileSync(configPath,
|
|
63
|
+
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
63
64
|
existing = JSON.parse(raw) as Record<string, unknown>;
|
|
64
65
|
} catch {
|
|
65
66
|
// File doesn't exist or is invalid — start fresh
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
// Deep-merge models.providers
|
|
69
|
+
// Deep-merge models.providers for all Parall-managed providers
|
|
70
|
+
// (preserve existing fields, overlay platform, inject credentials).
|
|
69
71
|
const models = (existing.models ?? {}) as Record<string, unknown>;
|
|
70
72
|
const providers = (models.providers ?? {}) as Record<string, unknown>;
|
|
71
|
-
const existingParall = (providers.parall ?? {}) as Record<string, unknown>;
|
|
72
73
|
const platformModels = (platformConfig.models ?? {}) as Record<string, unknown>;
|
|
73
74
|
const platformProviders = (platformModels.providers ?? {}) as Record<string, unknown>;
|
|
74
|
-
const platformParall = (platformProviders.parall ?? {}) as Record<string, unknown>;
|
|
75
75
|
|
|
76
76
|
// Only forward keys OpenClaw's model schema recognizes — its strict Zod
|
|
77
77
|
// validation rejects unrecognized keys and skips the entire config reload.
|
|
78
|
-
const OPENCLAW_MODEL_KEYS = new Set([
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
const OPENCLAW_MODEL_KEYS = new Set(['id', 'name', 'contextWindow', 'maxTokens']);
|
|
79
|
+
|
|
80
|
+
// Parall-managed providers: overlay when present in the platform payload.
|
|
81
|
+
// parall-anthropic is deleted when absent (rollback safety — stale provider
|
|
82
|
+
// would route to anthropic-messages transport that older servers can't auth).
|
|
83
|
+
// parall is never deleted — it's the core provider; a malformed/empty
|
|
84
|
+
// response should not wipe out the agent's only LLM access.
|
|
85
|
+
const PARALL_MANAGED_PROVIDERS = ['parall', 'parall-anthropic'] as const;
|
|
86
|
+
for (const providerName of PARALL_MANAGED_PROVIDERS) {
|
|
87
|
+
const rawProvider = platformProviders[providerName];
|
|
88
|
+
const platformProvider =
|
|
89
|
+
rawProvider && typeof rawProvider === 'object' && !Array.isArray(rawProvider)
|
|
90
|
+
? (rawProvider as Record<string, unknown>)
|
|
91
|
+
: null;
|
|
92
|
+
if (!platformProvider) {
|
|
93
|
+
if (providerName !== 'parall') delete providers[providerName];
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (Array.isArray(platformProvider.models)) {
|
|
98
|
+
platformProvider.models = (platformProvider.models as Record<string, unknown>[]).map((m) => {
|
|
99
|
+
const cleaned: Record<string, unknown> = {};
|
|
100
|
+
for (const [k, v] of Object.entries(m)) {
|
|
101
|
+
if (OPENCLAW_MODEL_KEYS.has(k)) {
|
|
102
|
+
cleaned[k] = v;
|
|
103
|
+
}
|
|
85
104
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
105
|
+
return cleaned;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
providers[providerName] = {
|
|
110
|
+
...((providers[providerName] ?? {}) as Record<string, unknown>),
|
|
111
|
+
...platformProvider,
|
|
112
|
+
apiKey: credentials.api_key,
|
|
113
|
+
};
|
|
89
114
|
}
|
|
90
115
|
|
|
91
|
-
providers.parall = {
|
|
92
|
-
...existingParall,
|
|
93
|
-
...platformParall,
|
|
94
|
-
apiKey: credentials.api_key,
|
|
95
|
-
};
|
|
96
116
|
models.providers = providers;
|
|
97
117
|
existing.models = models;
|
|
98
118
|
|
|
99
119
|
// Strip keys OpenClaw doesn't recognize from agents.defaults — platform-only
|
|
100
120
|
// keys like thinking_effort are consumed by agent-core's PlatformConfigManager.
|
|
101
121
|
// See: OpenClaw agents.defaults config schema.
|
|
102
|
-
const OPENCLAW_AGENTS_DEFAULTS_KEYS = new Set([
|
|
122
|
+
const OPENCLAW_AGENTS_DEFAULTS_KEYS = new Set(['model', 'compaction', 'memorySearch']);
|
|
123
|
+
// `model` is platform-sourced (delivered only on the catalog-gated Parall
|
|
124
|
+
// route) — it must NOT be carried over from the existing file, otherwise a
|
|
125
|
+
// stale `parall/...` model survives a switch to runtime_auth (the server then
|
|
126
|
+
// omits agents.defaults entirely) and the runtime keeps using the Parall
|
|
127
|
+
// provider. compaction / memorySearch are operator-tunable, so preserve those
|
|
128
|
+
// across applies; model is re-sourced from the platform overlay below or
|
|
129
|
+
// dropped when the platform sends none.
|
|
130
|
+
const OPENCLAW_PRESERVE_KEYS = new Set(['compaction', 'memorySearch']);
|
|
103
131
|
|
|
104
132
|
// Unconditionally sanitize existing defaults to self-heal already-poisoned
|
|
105
133
|
// openclaw.json files (e.g. mode=self agents that were poisoned while in
|
|
@@ -108,7 +136,7 @@ function applyToOpenClawConfig(
|
|
|
108
136
|
const existingDefaults = (agents.defaults ?? {}) as Record<string, unknown>;
|
|
109
137
|
const cleanedExisting: Record<string, unknown> = {};
|
|
110
138
|
for (const [k, v] of Object.entries(existingDefaults)) {
|
|
111
|
-
if (
|
|
139
|
+
if (OPENCLAW_PRESERVE_KEYS.has(k)) {
|
|
112
140
|
cleanedExisting[k] = v;
|
|
113
141
|
}
|
|
114
142
|
}
|
|
@@ -119,7 +147,7 @@ function applyToOpenClawConfig(
|
|
|
119
147
|
if (
|
|
120
148
|
rawDefaults !== undefined &&
|
|
121
149
|
rawDefaults !== null &&
|
|
122
|
-
typeof rawDefaults ===
|
|
150
|
+
typeof rawDefaults === 'object' &&
|
|
123
151
|
!Array.isArray(rawDefaults)
|
|
124
152
|
) {
|
|
125
153
|
const raw = rawDefaults as Record<string, unknown>;
|
|
@@ -130,6 +158,19 @@ function applyToOpenClawConfig(
|
|
|
130
158
|
}
|
|
131
159
|
}
|
|
132
160
|
|
|
161
|
+
// Bidirectional model rewrite for parall-anthropic. Gate on the fresh
|
|
162
|
+
// platform payload so rollback self-heals without manual intervention.
|
|
163
|
+
if (typeof cleanedExisting.model === 'string') {
|
|
164
|
+
const model = cleanedExisting.model as string;
|
|
165
|
+
if (platformProviders['parall-anthropic']) {
|
|
166
|
+
const fwd = model.match(/^parall\/(anthropic\/.+)$/);
|
|
167
|
+
if (fwd) cleanedExisting.model = `parall-anthropic/${fwd[1]}`;
|
|
168
|
+
} else {
|
|
169
|
+
const rev = model.match(/^parall-anthropic\/(anthropic\/.+)$/);
|
|
170
|
+
if (rev) cleanedExisting.model = `parall/${rev[1]}`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
133
174
|
agents.defaults = cleanedExisting;
|
|
134
175
|
existing.agents = agents;
|
|
135
176
|
|
|
@@ -138,7 +179,7 @@ function applyToOpenClawConfig(
|
|
|
138
179
|
// Atomic write
|
|
139
180
|
const tmpPath = `${configPath}.tmp`;
|
|
140
181
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
141
|
-
fs.writeFileSync(tmpPath, JSON.stringify(existing, null, 2),
|
|
182
|
+
fs.writeFileSync(tmpPath, JSON.stringify(existing, null, 2), 'utf-8');
|
|
142
183
|
fs.renameSync(tmpPath, configPath);
|
|
143
184
|
}
|
|
144
185
|
|
|
@@ -159,7 +200,9 @@ export async function fetchAndApplyPlatformConfig(opts: ConfigManagerOpts): Prom
|
|
|
159
200
|
} catch (err) {
|
|
160
201
|
// Fetch failed — fall through to use cache
|
|
161
202
|
if (cached) {
|
|
162
|
-
log?.warn(
|
|
203
|
+
log?.warn(
|
|
204
|
+
`platform config fetch failed, using cached version ${cached.version}: ${String(err)}`,
|
|
205
|
+
);
|
|
163
206
|
applyToOpenClawConfig(configPath, cached.config, credentials);
|
|
164
207
|
return;
|
|
165
208
|
}
|
|
@@ -170,7 +213,7 @@ export async function fetchAndApplyPlatformConfig(opts: ConfigManagerOpts): Prom
|
|
|
170
213
|
|
|
171
214
|
// 3. 304 — config unchanged
|
|
172
215
|
if (fresh === null) {
|
|
173
|
-
log?.info(
|
|
216
|
+
log?.info('platform config unchanged (304)');
|
|
174
217
|
if (cached) {
|
|
175
218
|
applyToOpenClawConfig(configPath, cached.config, credentials);
|
|
176
219
|
}
|
|
@@ -180,7 +223,9 @@ export async function fetchAndApplyPlatformConfig(opts: ConfigManagerOpts): Prom
|
|
|
180
223
|
// 4. Validate schema compatibility before accepting
|
|
181
224
|
const SUPPORTED_SCHEMA_VERSION = 1;
|
|
182
225
|
if (fresh.schema_version !== undefined && fresh.schema_version > SUPPORTED_SCHEMA_VERSION) {
|
|
183
|
-
log?.error(
|
|
226
|
+
log?.error(
|
|
227
|
+
`platform config schema_version ${fresh.schema_version} is newer than supported (${SUPPORTED_SCHEMA_VERSION}), keeping current config`,
|
|
228
|
+
);
|
|
184
229
|
if (cached) {
|
|
185
230
|
applyToOpenClawConfig(configPath, cached.config, credentials);
|
|
186
231
|
}
|
package/src/fork.ts
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
// branch point, the fork falls back to the current on-disk leaf — which
|
|
8
8
|
// may include partial in-flight state.
|
|
9
9
|
|
|
10
|
-
import * as fs from
|
|
11
|
-
import * as path from
|
|
12
|
-
import * as crypto from
|
|
13
|
-
import { CURRENT_SESSION_VERSION, SessionManager } from
|
|
10
|
+
import * as fs from 'node:fs';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
import * as crypto from 'node:crypto';
|
|
13
|
+
import { CURRENT_SESSION_VERSION, SessionManager } from './oc-session.js';
|
|
14
14
|
|
|
15
15
|
export type ForkSessionResult = {
|
|
16
16
|
sessionKey: string;
|
|
@@ -25,26 +25,33 @@ type SessionStoreEntry = {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
function readStoreEntry(sessionsDir: string, sessionKey: string): SessionStoreEntry | null {
|
|
28
|
-
const storeFile = path.join(sessionsDir,
|
|
28
|
+
const storeFile = path.join(sessionsDir, 'sessions.json');
|
|
29
29
|
try {
|
|
30
|
-
const store = JSON.parse(fs.readFileSync(storeFile,
|
|
30
|
+
const store = JSON.parse(fs.readFileSync(storeFile, 'utf-8')) as Record<
|
|
31
|
+
string,
|
|
32
|
+
SessionStoreEntry
|
|
33
|
+
>;
|
|
31
34
|
return store[sessionKey] ?? store[sessionKey.toLowerCase()] ?? null;
|
|
32
35
|
} catch {
|
|
33
36
|
return null;
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
function writeStoreEntry(
|
|
38
|
-
|
|
40
|
+
function writeStoreEntry(
|
|
41
|
+
sessionsDir: string,
|
|
42
|
+
sessionKey: string,
|
|
43
|
+
entry: SessionStoreEntry,
|
|
44
|
+
): boolean {
|
|
45
|
+
const storeFile = path.join(sessionsDir, 'sessions.json');
|
|
39
46
|
try {
|
|
40
47
|
let store: Record<string, SessionStoreEntry> = {};
|
|
41
48
|
try {
|
|
42
|
-
store = JSON.parse(fs.readFileSync(storeFile,
|
|
49
|
+
store = JSON.parse(fs.readFileSync(storeFile, 'utf-8'));
|
|
43
50
|
} catch {
|
|
44
51
|
// Empty or missing — start fresh.
|
|
45
52
|
}
|
|
46
53
|
store[sessionKey.toLowerCase()] = entry;
|
|
47
|
-
fs.writeFileSync(storeFile, JSON.stringify(store, null, 2), { encoding:
|
|
54
|
+
fs.writeFileSync(storeFile, JSON.stringify(store, null, 2), { encoding: 'utf-8' });
|
|
48
55
|
return true;
|
|
49
56
|
} catch {
|
|
50
57
|
return false;
|
|
@@ -52,12 +59,12 @@ function writeStoreEntry(sessionsDir: string, sessionKey: string, entry: Session
|
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
function deleteStoreEntry(sessionsDir: string, sessionKey: string): void {
|
|
55
|
-
const storeFile = path.join(sessionsDir,
|
|
62
|
+
const storeFile = path.join(sessionsDir, 'sessions.json');
|
|
56
63
|
try {
|
|
57
|
-
const store = JSON.parse(fs.readFileSync(storeFile,
|
|
64
|
+
const store = JSON.parse(fs.readFileSync(storeFile, 'utf-8')) as Record<string, unknown>;
|
|
58
65
|
delete store[sessionKey];
|
|
59
66
|
delete store[sessionKey.toLowerCase()];
|
|
60
|
-
fs.writeFileSync(storeFile, JSON.stringify(store, null, 2), { encoding:
|
|
67
|
+
fs.writeFileSync(storeFile, JSON.stringify(store, null, 2), { encoding: 'utf-8' });
|
|
61
68
|
} catch {
|
|
62
69
|
// Best-effort cleanup.
|
|
63
70
|
}
|
|
@@ -83,7 +90,7 @@ export function resolveTranscriptFile(sessionsDir: string, sessionKey: string):
|
|
|
83
90
|
|
|
84
91
|
try {
|
|
85
92
|
const files = fs.readdirSync(sessionsDir);
|
|
86
|
-
const match = files.find((file) => file.includes(entry.sessionId!) && file.endsWith(
|
|
93
|
+
const match = files.find((file) => file.includes(entry.sessionId!) && file.endsWith('.jsonl'));
|
|
87
94
|
return match ? path.join(sessionsDir, match) : null;
|
|
88
95
|
} catch {
|
|
89
96
|
return null;
|
|
@@ -139,13 +146,10 @@ export function forkOrchestratorSession(opts: {
|
|
|
139
146
|
// Fallback: no leaf node — create a header-only fork file manually.
|
|
140
147
|
sessionId = crypto.randomUUID();
|
|
141
148
|
const timestamp = new Date().toISOString();
|
|
142
|
-
const fileTimestamp = timestamp.replace(/[:.]/g,
|
|
143
|
-
sessionFile = path.join(
|
|
144
|
-
manager.getSessionDir(),
|
|
145
|
-
`${fileTimestamp}_${sessionId}.jsonl`,
|
|
146
|
-
);
|
|
149
|
+
const fileTimestamp = timestamp.replace(/[:.]/g, '-');
|
|
150
|
+
sessionFile = path.join(manager.getSessionDir(), `${fileTimestamp}_${sessionId}.jsonl`);
|
|
147
151
|
const header = {
|
|
148
|
-
type:
|
|
152
|
+
type: 'session',
|
|
149
153
|
version: CURRENT_SESSION_VERSION,
|
|
150
154
|
id: sessionId,
|
|
151
155
|
timestamp,
|
|
@@ -153,9 +157,9 @@ export function forkOrchestratorSession(opts: {
|
|
|
153
157
|
parentSession: transcriptFile,
|
|
154
158
|
};
|
|
155
159
|
fs.writeFileSync(sessionFile, `${JSON.stringify(header)}\n`, {
|
|
156
|
-
encoding:
|
|
160
|
+
encoding: 'utf-8',
|
|
157
161
|
mode: 0o600,
|
|
158
|
-
flag:
|
|
162
|
+
flag: 'wx',
|
|
159
163
|
});
|
|
160
164
|
}
|
|
161
165
|
|