@parall/agent-core 1.36.0 → 1.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge-workspace.d.ts +1 -1
- package/dist/bridge-workspace.d.ts.map +1 -1
- package/dist/bridge-workspace.js +13 -3
- package/dist/event-format.d.ts.map +1 -1
- package/dist/event-format.js +36 -1
- package/dist/gateway-base.d.ts +2 -0
- package/dist/gateway-base.d.ts.map +1 -1
- package/dist/gateway-base.js +132 -6
- package/dist/platform-config.d.ts +19 -0
- package/dist/platform-config.d.ts.map +1 -1
- package/dist/platform-config.js +72 -9
- package/dist/prompt-fragments.d.ts +1 -1
- package/dist/prompt-fragments.d.ts.map +1 -1
- package/dist/prompt-fragments.js +2 -0
- package/dist/skills/parall-platform.d.ts +1 -1
- package/dist/skills/parall-platform.d.ts.map +1 -1
- package/dist/skills/parall-platform.js +77 -6
- package/dist/types.d.ts +9 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/bridge-workspace.ts +13 -3
- package/src/event-format.ts +38 -1
- package/src/gateway-base.ts +149 -6
- package/src/platform-config.ts +85 -9
- package/src/prompt-fragments.ts +2 -0
- package/src/skills/parall-platform.ts +77 -6
- package/src/types.ts +15 -1
package/src/platform-config.ts
CHANGED
|
@@ -6,6 +6,11 @@ import type { GatewayLogger } from './dispatch-adapter.js';
|
|
|
6
6
|
export interface PlatformDefaults {
|
|
7
7
|
model: string | null;
|
|
8
8
|
thinkingEffort: string | null;
|
|
9
|
+
// Optional so adding it didn't break existing constructors of this exported
|
|
10
|
+
// type. Absent/undefined = old server didn't send model_is_pin → fall back to
|
|
11
|
+
// model_management; true = operator PIN (override, beats env); false = catalog
|
|
12
|
+
// FLOOR (loses to env).
|
|
13
|
+
modelIsPin?: boolean;
|
|
9
14
|
}
|
|
10
15
|
|
|
11
16
|
export interface PlatformConfigManager {
|
|
@@ -19,6 +24,12 @@ export interface PlatformManagementProfile {
|
|
|
19
24
|
model_management?: string | null;
|
|
20
25
|
}
|
|
21
26
|
|
|
27
|
+
/**
|
|
28
|
+
* @deprecated Bridges now derive pin-vs-floor via {@link deriveModelIsPin}
|
|
29
|
+
* (presence-gated dual-read of the server's `model_is_pin`). This is retained
|
|
30
|
+
* only as the legacy fallback inside `deriveModelIsPin` (for old servers that
|
|
31
|
+
* omit the field) and for any third-party consumers. Prefer `deriveModelIsPin`.
|
|
32
|
+
*/
|
|
22
33
|
export function isPlatformManagedProfile(
|
|
23
34
|
profile: PlatformManagementProfile | null | undefined,
|
|
24
35
|
): boolean {
|
|
@@ -34,6 +45,10 @@ export function isPlatformManagedProfile(
|
|
|
34
45
|
* are not. Pass the result as `resolveRuntimeModel`'s first argument. Keeping
|
|
35
46
|
* this derivation in one place avoids drift across the bridges' (re)config
|
|
36
47
|
* sites where the subtle null-hosted-vs-explicit-self distinction matters.
|
|
48
|
+
*
|
|
49
|
+
* @deprecated Used only as the legacy fallback inside {@link deriveModelIsPin}
|
|
50
|
+
* (old servers that omit `model_is_pin`). New code should read `model_is_pin`
|
|
51
|
+
* via `deriveModelIsPin` rather than calling this directly.
|
|
37
52
|
*/
|
|
38
53
|
export function isPlatformModelOverride(
|
|
39
54
|
profile: PlatformManagementProfile | null | undefined,
|
|
@@ -64,6 +79,19 @@ export function resolveRuntimeModel(
|
|
|
64
79
|
return operatorOverride ?? configModel ?? platformModel ?? undefined;
|
|
65
80
|
}
|
|
66
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Whether the platform-delivered model is an operator PIN (override) vs a catalog
|
|
84
|
+
* FLOOR. Presence-gated dual-read: a new server sends `model_is_pin` (use it); an
|
|
85
|
+
* old server omits it (`undefined`) so we fall back to the legacy
|
|
86
|
+
* `model_management`-derived signal. Pass the result to `resolveRuntimeModel`.
|
|
87
|
+
*/
|
|
88
|
+
export function deriveModelIsPin(
|
|
89
|
+
defaults: PlatformDefaults,
|
|
90
|
+
profile: PlatformManagementProfile | null | undefined,
|
|
91
|
+
): boolean {
|
|
92
|
+
return defaults.modelIsPin !== undefined ? defaults.modelIsPin : isPlatformModelOverride(profile);
|
|
93
|
+
}
|
|
94
|
+
|
|
67
95
|
interface PlatformModelDef {
|
|
68
96
|
id?: unknown;
|
|
69
97
|
runtime_names?: unknown;
|
|
@@ -72,6 +100,9 @@ interface PlatformModelDef {
|
|
|
72
100
|
interface CachedPlatformConfig {
|
|
73
101
|
version: string;
|
|
74
102
|
config: Record<string, unknown>;
|
|
103
|
+
// Top-level sibling of `config` (model_is_pin lives on the response, not inside
|
|
104
|
+
// config). Old cache files lack it → undefined → bridge falls back.
|
|
105
|
+
modelIsPin?: boolean;
|
|
75
106
|
fetchedAt: string;
|
|
76
107
|
}
|
|
77
108
|
|
|
@@ -95,6 +126,7 @@ function saveCache(stateDir: string, response: PlatformConfigResponse): void {
|
|
|
95
126
|
const cached: CachedPlatformConfig = {
|
|
96
127
|
version: response.version,
|
|
97
128
|
config: response.config,
|
|
129
|
+
modelIsPin: response.model_is_pin,
|
|
98
130
|
fetchedAt: new Date().toISOString(),
|
|
99
131
|
};
|
|
100
132
|
const filePath = cachePath(stateDir);
|
|
@@ -110,25 +142,49 @@ function runtimeModelName(
|
|
|
110
142
|
config: Record<string, unknown>,
|
|
111
143
|
): string | null {
|
|
112
144
|
const runtime = runtimeType?.trim();
|
|
113
|
-
if (!runtime || runtime === 'openclaw') return canonicalModel;
|
|
145
|
+
if (!runtime || runtime === 'openclaw' || runtime === 'hermes') return canonicalModel;
|
|
114
146
|
|
|
115
147
|
const models = (config.models ?? {}) as Record<string, unknown>;
|
|
116
148
|
const providers = (models.providers ?? {}) as Record<string, unknown>;
|
|
117
149
|
const parall = (providers.parall ?? {}) as Record<string, unknown>;
|
|
118
150
|
const catalog = Array.isArray(parall.models) ? (parall.models as PlatformModelDef[]) : [];
|
|
119
151
|
const match = catalog.find((model) => model.id === canonicalModel);
|
|
120
|
-
|
|
152
|
+
// Unknown to the catalog: let the bridge fall back to env/default.
|
|
153
|
+
if (!match) return null;
|
|
154
|
+
const runtimeNames = (match.runtime_names ?? {}) as Record<string, unknown>;
|
|
121
155
|
const runtimeName = runtimeNames[runtime];
|
|
122
|
-
|
|
156
|
+
if (typeof runtimeName === 'string' && runtimeName) return runtimeName;
|
|
157
|
+
// Cross-family on the Parall proxy route: this catalog model carries no
|
|
158
|
+
// native name for this runtime's CLI. Fall back to the canonical
|
|
159
|
+
// provider/model id — the Parall proxy accepts it and routes via OpenRouter's
|
|
160
|
+
// universal skin, so the CLI's native wire format still reaches the upstream.
|
|
161
|
+
// Mirrors the server's RuntimeModelName; only reachable on the Parall route
|
|
162
|
+
// (own routes are family-locked at write time to same-family models).
|
|
163
|
+
return canonicalModel;
|
|
123
164
|
}
|
|
124
165
|
|
|
125
|
-
|
|
166
|
+
// Exported for unit testing of the proxy-prefix normalization (see
|
|
167
|
+
// test/platform-config.test.mjs). Not part of the bridge-facing API surface.
|
|
168
|
+
// modelIsPin is NOT extracted here — it lives on the response/cache envelope
|
|
169
|
+
// (a sibling of `config`), so callers compose it in themselves.
|
|
170
|
+
export function extractDefaults(
|
|
171
|
+
config: Record<string, unknown>,
|
|
172
|
+
runtimeType: string | undefined,
|
|
173
|
+
): Omit<PlatformDefaults, 'modelIsPin'> {
|
|
126
174
|
const agents = (config.agents ?? {}) as Record<string, unknown>;
|
|
127
175
|
const defaults = (agents.defaults ?? {}) as Record<string, unknown>;
|
|
128
176
|
|
|
129
177
|
let model: string | null = null;
|
|
130
178
|
if (typeof defaults.model === 'string' && defaults.model) {
|
|
131
|
-
|
|
179
|
+
// Strip the proxy prefix before catalog translation. OpenClaw writers may
|
|
180
|
+
// rewrite parall/anthropic/... → parall-anthropic/anthropic/... (prompt-cache
|
|
181
|
+
// transport); both are proxy forms and a shared LKG cache can hold either.
|
|
182
|
+
// Strip parall-anthropic/ first, then any remaining parall/ — sequential
|
|
183
|
+
// (not either/or) and both via the same anchored regex, so a doubly-prefixed
|
|
184
|
+
// value normalizes fully and the two branches can't diverge on edge cases.
|
|
185
|
+
const canonicalModel = defaults.model
|
|
186
|
+
.replace(/^parall-anthropic\//, '')
|
|
187
|
+
.replace(/^parall\//, '');
|
|
132
188
|
model = runtimeModelName(canonicalModel, runtimeType, config);
|
|
133
189
|
}
|
|
134
190
|
|
|
@@ -148,14 +204,31 @@ export function createPlatformConfigManager(opts: {
|
|
|
148
204
|
}): PlatformConfigManager {
|
|
149
205
|
const { client, stateDir, runtimeType, log } = opts;
|
|
150
206
|
let cachedVersion: string | undefined;
|
|
151
|
-
let currentDefaults: PlatformDefaults = {
|
|
207
|
+
let currentDefaults: PlatformDefaults = {
|
|
208
|
+
model: null,
|
|
209
|
+
thinkingEffort: null,
|
|
210
|
+
modelIsPin: undefined,
|
|
211
|
+
};
|
|
152
212
|
let currentRawConfig: Record<string, unknown> | null = null;
|
|
153
213
|
|
|
154
214
|
const cached = loadCache(stateDir);
|
|
155
215
|
if (cached) {
|
|
156
|
-
|
|
216
|
+
// Pre-model_is_pin cache schema (no modelIsPin field): keep the LKG
|
|
217
|
+
// defaults, but DROP the ETag so the next fetch returns a full 200 and
|
|
218
|
+
// rewrites the cache in the new schema. With the ETag, a server whose
|
|
219
|
+
// config hasn't changed would 304 forever and strand
|
|
220
|
+
// modelIsPin=undefined (permanent legacy model_management fallback) on a
|
|
221
|
+
// fully-upgraded stack — the rollout order "server first, bridge later"
|
|
222
|
+
// makes an old-schema cache holding a CURRENT ETag the normal case.
|
|
223
|
+
// Against an old server (which never sends model_is_pin) this costs one
|
|
224
|
+
// full fetch per process start; acceptable, and disappears once the
|
|
225
|
+
// server is upgraded.
|
|
226
|
+
cachedVersion = cached.modelIsPin === undefined ? undefined : cached.version;
|
|
157
227
|
currentRawConfig = cached.config;
|
|
158
|
-
currentDefaults =
|
|
228
|
+
currentDefaults = {
|
|
229
|
+
...extractDefaults(cached.config, runtimeType),
|
|
230
|
+
modelIsPin: cached.modelIsPin,
|
|
231
|
+
};
|
|
159
232
|
}
|
|
160
233
|
|
|
161
234
|
return {
|
|
@@ -189,7 +262,10 @@ export function createPlatformConfigManager(opts: {
|
|
|
189
262
|
saveCache(stateDir, fresh);
|
|
190
263
|
cachedVersion = fresh.version;
|
|
191
264
|
currentRawConfig = fresh.config;
|
|
192
|
-
currentDefaults =
|
|
265
|
+
currentDefaults = {
|
|
266
|
+
...extractDefaults(fresh.config, runtimeType),
|
|
267
|
+
modelIsPin: fresh.model_is_pin,
|
|
268
|
+
};
|
|
193
269
|
return currentDefaults;
|
|
194
270
|
},
|
|
195
271
|
|
package/src/prompt-fragments.ts
CHANGED
|
@@ -199,6 +199,8 @@ Or upload first and reuse across chats:
|
|
|
199
199
|
parall messages send prll://cht_aaa --attachment att_yyy --text "Report"
|
|
200
200
|
parall messages send prll://cht_bbb --attachment att_yyy --text "FYI"
|
|
201
201
|
|
|
202
|
+
The \`--text\` captions above are safe short literals. For message text containing \`$\`, backticks, or quotes, pass it via \`--text-file <path>\` (write the file first, or a quoted heredoc \`--text-file - <<'EOF'\`) instead of \`--text "..."\` — inside double quotes the shell turns \`$1,000\` into \`,000\` and executes \`$(...)\`.
|
|
203
|
+
|
|
202
204
|
### When to reference
|
|
203
205
|
|
|
204
206
|
- **Origin** — always link the message or task that triggered your work
|
|
@@ -64,22 +64,69 @@ parall machines logs prll://mch_xxx --lines 100
|
|
|
64
64
|
\`\`\`bash
|
|
65
65
|
parall chats list # List all chats
|
|
66
66
|
parall messages list prll://cht_xxx # Read chat message history
|
|
67
|
+
parall messages list prll://cht_xxx --since 2026-01-01 # Only messages at/after a date (RFC3339 or YYYY-MM-DD)
|
|
67
68
|
\`\`\`
|
|
68
69
|
|
|
70
|
+
## Org-Context Search
|
|
71
|
+
|
|
72
|
+
Before deciding or starting non-trivial work, search the org's real history —
|
|
73
|
+
past discussions, decisions, tasks, and wiki notes — so you don't re-litigate
|
|
74
|
+
settled questions or repeat known mistakes. This searches live org data
|
|
75
|
+
(semantic + keyword), not a local copy, and is permission-filtered to what you
|
|
76
|
+
can see.
|
|
77
|
+
|
|
78
|
+
\`\`\`bash
|
|
79
|
+
# Semantic + keyword search across messages, tasks, and wiki
|
|
80
|
+
parall search "auth v5 upgrade"
|
|
81
|
+
|
|
82
|
+
# Restrict entity types (m=message, t=task, w=wiki). --channel narrows the
|
|
83
|
+
# MESSAGE hits to one chat (tasks/wiki are unaffected by it).
|
|
84
|
+
parall search "auth v5 upgrade" --types m,w --channel prll://cht_eng
|
|
85
|
+
|
|
86
|
+
# Time-box to recent activity (RFC3339 or YYYY-MM-DD). Narrows messages + tasks;
|
|
87
|
+
# wiki is always matched by relevance (the index has no authored timestamp).
|
|
88
|
+
parall search "auth v5 upgrade" --since 2026-01-01
|
|
89
|
+
|
|
90
|
+
# Narrow wiki hits to a frontmatter document type
|
|
91
|
+
parall search "deploy steps" --types w --wiki-type Runbook
|
|
92
|
+
\`\`\`
|
|
93
|
+
|
|
94
|
+
Even with zero curated notes, the raw message + task history is searchable — the
|
|
95
|
+
original discussion and its approval/rejection IS the precedent.
|
|
96
|
+
|
|
69
97
|
## Sending Messages
|
|
70
98
|
|
|
71
99
|
Each \`[Event: message.new]\` includes \`[Chat: ... (prll://cht_xxx)]\` — use that chat URI to reply.
|
|
72
100
|
|
|
101
|
+
> **How you pass the message body matters — your command runs through a shell.**
|
|
102
|
+
> Inside double quotes the shell expands \`$\`, backticks, and \`$(...)\` *before*
|
|
103
|
+
> the CLI sees them: \`--text "That costs $1,000"\` sends \`That costs ,000\`, and
|
|
104
|
+
> \`--text "$(cmd)"\` runs \`cmd\`. Single quotes instead break on apostrophes
|
|
105
|
+
> (\`I'm\`, \`don't\`). So do **not** wrap real message content in quotes — pass it
|
|
106
|
+
> through \`--text-file\` (a written file, or a quoted heredoc \`<<'EOF'\` that
|
|
107
|
+
> disables all expansion). Reserve \`--text "..."\` for short literals with no
|
|
108
|
+
> \`$\`, backtick, or apostrophe.
|
|
109
|
+
|
|
73
110
|
\`\`\`bash
|
|
74
|
-
#
|
|
75
|
-
|
|
111
|
+
# One-off reply → quoted heredoc into stdin. The quoted delimiter <<'EOF'
|
|
112
|
+
# disables ALL shell expansion, so $, backticks and apostrophes pass verbatim.
|
|
113
|
+
parall messages send prll://cht_xxx --text-file - <<'PARALL_EOF'
|
|
114
|
+
Sure — that's $1,000, and $(whoami) stays literal. I'm on it.
|
|
115
|
+
PARALL_EOF
|
|
76
116
|
|
|
77
|
-
#
|
|
78
|
-
|
|
117
|
+
# Longer / multi-line reply → write it with your file tool (no shell touches
|
|
118
|
+
# the body), then point --text-file at the file.
|
|
119
|
+
parall messages send prll://cht_xxx --text-file /tmp/reply.md
|
|
120
|
+
|
|
121
|
+
# Short literal with no $, backtick, or apostrophe → --text is fine.
|
|
122
|
+
parall messages send prll://cht_xxx --text "On it"
|
|
123
|
+
|
|
124
|
+
# Direct message by user URI or display name (same --text-file / heredoc rules)
|
|
125
|
+
parall dm prll://usr_xxx --text-file /tmp/reply.md
|
|
79
126
|
parall dm "Alice" --text "Hello"
|
|
80
127
|
|
|
81
128
|
# Thread reply
|
|
82
|
-
parall messages send prll://cht_xxx --text
|
|
129
|
+
parall messages send prll://cht_xxx --text-file /tmp/reply.md --thread-root-id 01JWC...
|
|
83
130
|
|
|
84
131
|
# FYI message (no response expected — the recipient sees \`[Hint: no_reply]\`)
|
|
85
132
|
parall messages send prll://cht_xxx --text "FYI: done" --no-reply
|
|
@@ -111,7 +158,9 @@ parall messages send prll://cht_xxx --attachment att_xxx --text "See attached"
|
|
|
111
158
|
parall dm "Alice" --file /tmp/report.pdf --text "Report attached"
|
|
112
159
|
\`\`\`
|
|
113
160
|
|
|
114
|
-
\`--file\` and \`--attachment\` are mutually exclusive. \`--text\`
|
|
161
|
+
\`--file\` and \`--attachment\` are mutually exclusive. A caption (\`--text\` for
|
|
162
|
+
short literals, or \`--text-file\` for anything with \`$\`, backticks, or quotes)
|
|
163
|
+
can be combined with either.
|
|
115
164
|
|
|
116
165
|
## Approvals
|
|
117
166
|
|
|
@@ -167,5 +216,27 @@ When a message or event references \`prll://sch_xxx\` or \`prll://srn_xxx\`, or
|
|
|
167
216
|
|
|
168
217
|
When a message or event references \`prll://xcn_xxx\`, \`prll://xin_xxx\`, \`prll://xtr_xxx\`, or \`prll://xrn_xxx\`, or when you receive \`[Event: external.trigger]\`, switch to the **parall-external-triggers** skill for the CLI commands (connections / triggers / events / runs).
|
|
169
218
|
|
|
219
|
+
## References (relationship graph)
|
|
220
|
+
|
|
221
|
+
\`prll://\` references between entities form a graph — a message cites a task, a
|
|
222
|
+
task cites a wiki page, and so on. Walk it to answer "what is this decision /
|
|
223
|
+
entity connected to". All results are permission-filtered to what you can see.
|
|
224
|
+
|
|
225
|
+
\`\`\`bash
|
|
226
|
+
# Resolve URIs to entity metadata (titles, status, previews)
|
|
227
|
+
parall refs resolve prll://tsk_xxx prll://wik_xxx
|
|
228
|
+
|
|
229
|
+
# Single hop — who references X
|
|
230
|
+
parall refs backlinks prll://tsk_xxx
|
|
231
|
+
|
|
232
|
+
# Multi-hop — the connected sub-graph around X (entity-level URI only — no
|
|
233
|
+
# path/anchor; depth 1–4, default 2)
|
|
234
|
+
parall refs graph prll://tsk_xxx --depth 2
|
|
235
|
+
\`\`\`
|
|
236
|
+
|
|
237
|
+
\`refs graph\` traverses both directions (inbound + outbound) and returns \`nodes\`
|
|
238
|
+
and \`edges\` with each node's hop \`depth\`. \`truncated: true\` means a size cap clipped
|
|
239
|
+
the result — narrow it with a smaller \`--depth\`.
|
|
240
|
+
|
|
170
241
|
CLI success output is JSON. Errors print a JSON line (\`{"error","status","code",...}\`) and, on a \`PERMISSION_DENIED\`, may add a plain-text \`Request approval:\` line — read both.
|
|
171
242
|
`;
|
package/src/types.ts
CHANGED
|
@@ -31,6 +31,7 @@ export type ParallEvent = {
|
|
|
31
31
|
| 'wiki_comment'
|
|
32
32
|
| 'schedule'
|
|
33
33
|
| 'external_trigger'
|
|
34
|
+
| 'channel_message'
|
|
34
35
|
| 'approval';
|
|
35
36
|
targetId: string;
|
|
36
37
|
targetName?: string;
|
|
@@ -65,10 +66,23 @@ export type ParallEvent = {
|
|
|
65
66
|
externalConnectionDisplayName?: string;
|
|
66
67
|
externalIngressEventId?: string;
|
|
67
68
|
externalIngressEventType?: string;
|
|
69
|
+
/** External IM channel metadata, used for channel_message events. */
|
|
70
|
+
channelProvider?: string;
|
|
71
|
+
channelConversationType?: string;
|
|
72
|
+
/** Provider-side conversation id (the send_message target). */
|
|
73
|
+
channelExternalConversationId?: string;
|
|
74
|
+
/** Provider-side message id (in-thread reply target). */
|
|
75
|
+
channelExternalMessageId?: string;
|
|
68
76
|
/** Original event timestamp (e.g., message.created_at). When present,
|
|
69
77
|
* input steps use this instead of server insertion time for ordering. */
|
|
70
78
|
sentAt?: string;
|
|
71
|
-
ackSourceType?:
|
|
79
|
+
ackSourceType?:
|
|
80
|
+
| 'message'
|
|
81
|
+
| 'task_activity'
|
|
82
|
+
| 'comment'
|
|
83
|
+
| 'schedule_run'
|
|
84
|
+
| 'external_trigger_run'
|
|
85
|
+
| 'channel_message';
|
|
72
86
|
ackSourceId?: string;
|
|
73
87
|
/** Unread message count in the target chat since agent's last interaction. */
|
|
74
88
|
unreadCount?: number;
|