@parall/parall 1.55.0 → 1.55.2
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/index.bundle.mjs +173 -14
- package/package.json +3 -3
- package/skills/parall-clip-authoring/SKILL.md +54 -7
package/dist/index.bundle.mjs
CHANGED
|
@@ -51878,6 +51878,13 @@ var ENDPOINTS = {
|
|
|
51878
51878
|
PROJECTS: (orgId) => `${API_BASE}/orgs/${orgId}/projects`,
|
|
51879
51879
|
PROJECT_TASK_SUMMARY: (orgId) => `${API_BASE}/orgs/${orgId}/projects/task-summary`,
|
|
51880
51880
|
PROJECT: (orgId, projectId) => `${API_BASE}/orgs/${orgId}/projects/${projectId}`,
|
|
51881
|
+
PROJECT_MEMBERS: (orgId, projectId) => `${API_BASE}/orgs/${orgId}/projects/${projectId}/members`,
|
|
51882
|
+
PROJECT_READERS: (orgId, projectId) => `${API_BASE}/orgs/${orgId}/projects/${projectId}/readers`,
|
|
51883
|
+
PROJECT_LIBRARY: (orgId) => `${API_BASE}/orgs/${orgId}/projects/library`,
|
|
51884
|
+
PROJECT_JOIN: (orgId, projectId) => `${API_BASE}/orgs/${orgId}/projects/${projectId}/join`,
|
|
51885
|
+
PROJECT_JOIN_REQUESTS: (orgId, projectId) => `${API_BASE}/orgs/${orgId}/projects/${projectId}/join-requests`,
|
|
51886
|
+
// subject is a bare user ID or a bare team ID; both are path-segment safe.
|
|
51887
|
+
PROJECT_MEMBER: (orgId, projectId, subject) => `${API_BASE}/orgs/${orgId}/projects/${projectId}/members/${subject}`,
|
|
51881
51888
|
// Schedules (org-scoped, platform time trigger primitive)
|
|
51882
51889
|
SCHEDULES: (orgId) => `${API_BASE}/orgs/${orgId}/schedules`,
|
|
51883
51890
|
SCHEDULE: (orgId, id) => `${API_BASE}/orgs/${orgId}/schedules/${id}`,
|
|
@@ -52095,6 +52102,9 @@ var ENDPOINTS = {
|
|
|
52095
52102
|
ORG_EDGE_PROFILES: (orgId, edgeId) => `/api/v1/orgs/${orgId}/edge/${edgeId}/profiles`,
|
|
52096
52103
|
// Per-profile egress proxy (hosted Cloud Profiles; manager-only, human-only).
|
|
52097
52104
|
ORG_EDGE_PROFILE_PROXY: (orgId, edgeId, profileName) => `/api/v1/orgs/${orgId}/edge/${edgeId}/profiles/${encodeURIComponent(profileName)}/proxy`,
|
|
52105
|
+
// Per-profile one-shot cookie seed (hosted Cloud Profiles; manager-only,
|
|
52106
|
+
// human-only, idle-only). Injected at the next cold start, then consumed.
|
|
52107
|
+
ORG_EDGE_PROFILE_COOKIES: (orgId, edgeId, profileName) => `/api/v1/orgs/${orgId}/edge/${edgeId}/profiles/${encodeURIComponent(profileName)}/cookies`,
|
|
52098
52108
|
ORG_EDGE_EXEC: (orgId) => `/api/v1/orgs/${orgId}/edge/exec`,
|
|
52099
52109
|
// Cloud Edge live viewer command (V1b) — api-server, gated on cap:edge-viewer.
|
|
52100
52110
|
// Same request/reply shape as the v2 browser-profile viewer, on the v3 edge
|
|
@@ -52136,6 +52146,7 @@ var WS_EVENTS = {
|
|
|
52136
52146
|
PONG: "pong",
|
|
52137
52147
|
WATCHING: "watching",
|
|
52138
52148
|
MESSAGE_NEW: "message.new",
|
|
52149
|
+
/** @deprecated `message.patch` is retired and never published; kept for compile compatibility. */
|
|
52139
52150
|
MESSAGE_PATCH: "message.patch",
|
|
52140
52151
|
MESSAGE_EDIT: "message.edit",
|
|
52141
52152
|
MESSAGE_DELETE: "message.delete",
|
|
@@ -52331,6 +52342,62 @@ var ProjectTaskClient = class {
|
|
|
52331
52342
|
async deleteProject(orgId, projectId) {
|
|
52332
52343
|
return this.request("DELETE", ENDPOINTS.PROJECT(orgId, projectId));
|
|
52333
52344
|
}
|
|
52345
|
+
/** Roster with display info; readable by anyone who can read the project. */
|
|
52346
|
+
async getProjectMembers(orgId, projectId) {
|
|
52347
|
+
const res = await this.request("GET", ENDPOINTS.PROJECT_MEMBERS(orgId, projectId));
|
|
52348
|
+
return res.data;
|
|
52349
|
+
}
|
|
52350
|
+
/**
|
|
52351
|
+
* User IDs who may read the project — the assignee-eligibility set. Every
|
|
52352
|
+
* tier answers the roster expansion (direct, team-reached, org
|
|
52353
|
+
* owners/admins); visibility only shapes admission, never reach.
|
|
52354
|
+
*/
|
|
52355
|
+
async getProjectReaders(orgId, projectId) {
|
|
52356
|
+
const res = await this.request("GET", ENDPOINTS.PROJECT_READERS(orgId, projectId));
|
|
52357
|
+
return res.data;
|
|
52358
|
+
}
|
|
52359
|
+
/** The join library: every discoverable project (public + restricted, plus
|
|
52360
|
+
* private for org owners/admins) with the caller's admission state. */
|
|
52361
|
+
async getProjectLibrary(orgId) {
|
|
52362
|
+
const res = await this.request("GET", ENDPOINTS.PROJECT_LIBRARY(orgId));
|
|
52363
|
+
return res.data;
|
|
52364
|
+
}
|
|
52365
|
+
/** Public-tier self-admission: writes the caller's own member row.
|
|
52366
|
+
* Other tiers answer `404 PROJECT_NOT_FOUND`; a duplicate answers
|
|
52367
|
+
* `409 MEMBER_EXISTS`. */
|
|
52368
|
+
async joinProject(orgId, projectId) {
|
|
52369
|
+
return this.request("POST", ENDPOINTS.PROJECT_JOIN(orgId, projectId));
|
|
52370
|
+
}
|
|
52371
|
+
/** Restricted-tier admission petition. A pending duplicate answers
|
|
52372
|
+
* `409 REQUEST_EXISTS`; membership answers `409 MEMBER_EXISTS`. */
|
|
52373
|
+
async createProjectJoinRequest(orgId, projectId) {
|
|
52374
|
+
return this.request("POST", ENDPOINTS.PROJECT_JOIN_REQUESTS(orgId, projectId));
|
|
52375
|
+
}
|
|
52376
|
+
/** Pending petitions for one project — manager standing required. */
|
|
52377
|
+
async listProjectJoinRequests(orgId, projectId) {
|
|
52378
|
+
const res = await this.request("GET", ENDPOINTS.PROJECT_JOIN_REQUESTS(orgId, projectId));
|
|
52379
|
+
return res.data;
|
|
52380
|
+
}
|
|
52381
|
+
/** Withdraws the caller's own pending petition. */
|
|
52382
|
+
async cancelProjectJoinRequest(orgId, projectId) {
|
|
52383
|
+
return this.request("DELETE", ENDPOINTS.PROJECT_JOIN_REQUESTS(orgId, projectId));
|
|
52384
|
+
}
|
|
52385
|
+
/**
|
|
52386
|
+
* Adds one subject (user ID or team ID) to the roster. Manager-only,
|
|
52387
|
+
* create-only: an existing row answers `409 MEMBER_EXISTS` — change roles
|
|
52388
|
+
* through updateProjectMember instead.
|
|
52389
|
+
*/
|
|
52390
|
+
async addProjectMember(orgId, projectId, req) {
|
|
52391
|
+
return this.request("POST", ENDPOINTS.PROJECT_MEMBERS(orgId, projectId), req);
|
|
52392
|
+
}
|
|
52393
|
+
/** Sets one roster entry's role. Demoting the last manager answers `400 LAST_MANAGER`. */
|
|
52394
|
+
async updateProjectMember(orgId, projectId, subject, req) {
|
|
52395
|
+
return this.request("PATCH", ENDPOINTS.PROJECT_MEMBER(orgId, projectId, subject), req);
|
|
52396
|
+
}
|
|
52397
|
+
/** Removes one roster entry. Removing the last manager answers `400 LAST_MANAGER`. */
|
|
52398
|
+
async removeProjectMember(orgId, projectId, subject) {
|
|
52399
|
+
return this.request("DELETE", ENDPOINTS.PROJECT_MEMBER(orgId, projectId, subject));
|
|
52400
|
+
}
|
|
52334
52401
|
};
|
|
52335
52402
|
|
|
52336
52403
|
// ../sdk/dist/task-label-client.js
|
|
@@ -52723,8 +52790,9 @@ var ParallClient = class _ParallClient extends TaskLabelClient {
|
|
|
52723
52790
|
return this.request("POST", ENDPOINTS.TEAMS(orgId), req);
|
|
52724
52791
|
}
|
|
52725
52792
|
/**
|
|
52726
|
-
* Renames a team.
|
|
52727
|
-
*
|
|
52793
|
+
* Renames a team. Renaming is an ordinary edit: ACL rows reference teams by
|
|
52794
|
+
* ID. Names are org-unique (case-insensitive); a duplicate answers 409
|
|
52795
|
+
* NAME_TAKEN.
|
|
52728
52796
|
*/
|
|
52729
52797
|
async updateTeam(orgId, teamId, req) {
|
|
52730
52798
|
return this.request("PATCH", ENDPOINTS.TEAM(orgId, teamId), req);
|
|
@@ -54353,15 +54421,18 @@ var ParallClient = class _ParallClient extends TaskLabelClient {
|
|
|
54353
54421
|
/**
|
|
54354
54422
|
* Read a clip's per-org agent exec access. Org-member readable (agents
|
|
54355
54423
|
* included, so a denied agent can learn why exec answered
|
|
54356
|
-
* `CLIP_AGENT_NOT_ALLOWED`).
|
|
54424
|
+
* `CLIP_AGENT_NOT_ALLOWED` / `CLIP_AGENT_CONNECTION_NOT_ALLOWED`).
|
|
54357
54425
|
*/
|
|
54358
54426
|
async getClipAgentExecAccess(orgId, clipId) {
|
|
54359
54427
|
return this.request("GET", ENDPOINTS.ORG_CLIP_EXEC_ACCESS(orgId, clipId));
|
|
54360
54428
|
}
|
|
54361
54429
|
/**
|
|
54362
54430
|
* Replace a clip's per-org agent exec access. Human-only (agent principals
|
|
54363
|
-
* get 403); under `all_agents`
|
|
54364
|
-
*
|
|
54431
|
+
* get 403); under `all_agents` both lists must be empty; pass either
|
|
54432
|
+
* `agent_ids` (legacy flat form → every agent gets `connection_scope: 'all'`)
|
|
54433
|
+
* or `grants` (connection-scoped), never both. Every granted id must be an
|
|
54434
|
+
* active agent of this org, and every `connection_ids` entry a connection of
|
|
54435
|
+
* THIS clip in this org (else `400 INVALID_INPUT`).
|
|
54365
54436
|
*/
|
|
54366
54437
|
async putClipAgentExecAccess(orgId, clipId, access) {
|
|
54367
54438
|
return this.request("PUT", ENDPOINTS.ORG_CLIP_EXEC_ACCESS(orgId, clipId), access);
|
|
@@ -54447,6 +54518,41 @@ var ParallClient = class _ParallClient extends TaskLabelClient {
|
|
|
54447
54518
|
headers: expectedVersion ? { "If-Match": `"proxy-${expectedVersion}"` } : void 0
|
|
54448
54519
|
});
|
|
54449
54520
|
}
|
|
54521
|
+
/**
|
|
54522
|
+
* Read a hosted Cloud Profile's one-shot cookie-seed status (manager-only:
|
|
54523
|
+
* hosted human maintainer or org admin). Sanitized — cookie VALUES never come
|
|
54524
|
+
* back, only `cookie_count` and the distinct target `domains`. `can_mutate` /
|
|
54525
|
+
* `lease_status` are the authoritative idle gate. Typed errors: `EDGE_NOT_HOSTED`
|
|
54526
|
+
* (BYOC device), `NOT_FOUND` (unknown profile).
|
|
54527
|
+
*/
|
|
54528
|
+
async getEdgeProfileCookieSeed(orgId, edgeId, profileName) {
|
|
54529
|
+
return this.request("GET", ENDPOINTS.ORG_EDGE_PROFILE_COOKIES(orgId, edgeId, profileName));
|
|
54530
|
+
}
|
|
54531
|
+
/**
|
|
54532
|
+
* Set/replace the profile's one-shot cookie seed (the full normalized cookie
|
|
54533
|
+
* array every time) — IDLE ONLY: while the profile's hosted browser is running
|
|
54534
|
+
* the server answers 409 `EDGE_PROFILE_IN_USE`; close the viewer, wait for idle
|
|
54535
|
+
* scale-to-zero, and retry. The next cold start injects the cookies into the
|
|
54536
|
+
* profile partition and consumes the seed. Other typed errors:
|
|
54537
|
+
* `EDGE_COOKIE_SEED_STALE` (409 — expectedVersion lost a tab race, sent as the
|
|
54538
|
+
* `cookieseed-<version>` If-Match), `EDGE_DELETING` (409), and
|
|
54539
|
+
* `SECRETBOX_UNCONFIGURED` (503 — server cannot store cookies safely).
|
|
54540
|
+
*/
|
|
54541
|
+
async setEdgeProfileCookieSeed(orgId, edgeId, profileName, req, expectedVersion) {
|
|
54542
|
+
return this.request("PUT", ENDPOINTS.ORG_EDGE_PROFILE_COOKIES(orgId, edgeId, profileName), req, void 0, false, {
|
|
54543
|
+
headers: expectedVersion ? { "If-Match": `"cookieseed-${expectedVersion}"` } : void 0
|
|
54544
|
+
});
|
|
54545
|
+
}
|
|
54546
|
+
/**
|
|
54547
|
+
* Clear the pending cookie seed (does NOT sign the profile out — cookies
|
|
54548
|
+
* already injected into the partition stay). Idle-only like set — 409
|
|
54549
|
+
* `EDGE_PROFILE_IN_USE` while the browser is live.
|
|
54550
|
+
*/
|
|
54551
|
+
async clearEdgeProfileCookieSeed(orgId, edgeId, profileName, expectedVersion) {
|
|
54552
|
+
return this.request("DELETE", ENDPOINTS.ORG_EDGE_PROFILE_COOKIES(orgId, edgeId, profileName), void 0, void 0, false, {
|
|
54553
|
+
headers: expectedVersion ? { "If-Match": `"cookieseed-${expectedVersion}"` } : void 0
|
|
54554
|
+
});
|
|
54555
|
+
}
|
|
54450
54556
|
/**
|
|
54451
54557
|
* Execute a registry clip command on an Edge device.
|
|
54452
54558
|
*
|
|
@@ -55794,6 +55900,56 @@ async function consumeMessageWorkItem(host, item) {
|
|
|
55794
55900
|
}
|
|
55795
55901
|
}
|
|
55796
55902
|
|
|
55903
|
+
// ../agent-core/dist/dispatch-inactivity-deadline.js
|
|
55904
|
+
var DispatchInactivityDeadline = class {
|
|
55905
|
+
timeoutMs;
|
|
55906
|
+
onExpire;
|
|
55907
|
+
onDispose;
|
|
55908
|
+
timer = null;
|
|
55909
|
+
expired = false;
|
|
55910
|
+
disposed = false;
|
|
55911
|
+
constructor(timeoutMs, onExpire, onDispose) {
|
|
55912
|
+
this.timeoutMs = timeoutMs;
|
|
55913
|
+
this.onExpire = onExpire;
|
|
55914
|
+
this.onDispose = onDispose;
|
|
55915
|
+
}
|
|
55916
|
+
touch = () => {
|
|
55917
|
+
if (this.timeoutMs <= 0 || this.expired || this.disposed)
|
|
55918
|
+
return;
|
|
55919
|
+
if (this.timer)
|
|
55920
|
+
clearTimeout(this.timer);
|
|
55921
|
+
this.timer = setTimeout(() => {
|
|
55922
|
+
this.timer = null;
|
|
55923
|
+
this.expired = true;
|
|
55924
|
+
this.onExpire();
|
|
55925
|
+
}, this.timeoutMs);
|
|
55926
|
+
};
|
|
55927
|
+
dispose() {
|
|
55928
|
+
if (this.disposed)
|
|
55929
|
+
return;
|
|
55930
|
+
this.disposed = true;
|
|
55931
|
+
if (this.timer)
|
|
55932
|
+
clearTimeout(this.timer);
|
|
55933
|
+
this.timer = null;
|
|
55934
|
+
this.onDispose();
|
|
55935
|
+
}
|
|
55936
|
+
};
|
|
55937
|
+
var DispatchInactivityDeadlines = class {
|
|
55938
|
+
active = /* @__PURE__ */ new Map();
|
|
55939
|
+
start(sessionKey, timeoutMs, onExpire) {
|
|
55940
|
+
const deadline = new DispatchInactivityDeadline(timeoutMs, onExpire, () => {
|
|
55941
|
+
if (this.active.get(sessionKey) === deadline)
|
|
55942
|
+
this.active.delete(sessionKey);
|
|
55943
|
+
});
|
|
55944
|
+
this.active.set(sessionKey, deadline);
|
|
55945
|
+
deadline.touch();
|
|
55946
|
+
return deadline;
|
|
55947
|
+
}
|
|
55948
|
+
touch(sessionKey) {
|
|
55949
|
+
this.active.get(sessionKey)?.touch();
|
|
55950
|
+
}
|
|
55951
|
+
};
|
|
55952
|
+
|
|
55797
55953
|
// ../agent-core/dist/routing.js
|
|
55798
55954
|
var MAX_CONCURRENT_FORKS = 20;
|
|
55799
55955
|
var defaultRoutingStrategy = (event, state) => {
|
|
@@ -57355,6 +57511,7 @@ var ParallAgentGateway = class {
|
|
|
57355
57511
|
* an unrelated stalled fork's lane leased forever).
|
|
57356
57512
|
*/
|
|
57357
57513
|
sessionActiveLanes = /* @__PURE__ */ new Map();
|
|
57514
|
+
dispatchInactivityDeadlines = new DispatchInactivityDeadlines();
|
|
57358
57515
|
noteSessionLane(sessionKey, laneKey) {
|
|
57359
57516
|
if (laneKey == null)
|
|
57360
57517
|
this.sessionActiveLanes.delete(sessionKey);
|
|
@@ -57364,11 +57521,12 @@ var ParallAgentGateway = class {
|
|
|
57364
57521
|
/**
|
|
57365
57522
|
* External runtime-activity signal for adapters whose tool activity does
|
|
57366
57523
|
* not flow through the RuntimeEvent stream (openclaw hooks call this from
|
|
57367
|
-
* the tool-call lifecycle):
|
|
57368
|
-
*
|
|
57369
|
-
*
|
|
57524
|
+
* the tool-call lifecycle): refreshes the dispatch inactivity deadline and
|
|
57525
|
+
* renews the session's OWN active ledger lane, when present, so a long tool
|
|
57526
|
+
* call cannot time out or get dethroned mid-turn.
|
|
57370
57527
|
*/
|
|
57371
57528
|
touchRuntimeActivity(sessionKey) {
|
|
57529
|
+
this.dispatchInactivityDeadlines.touch(sessionKey);
|
|
57372
57530
|
if (this.ledgerDisabled)
|
|
57373
57531
|
return;
|
|
57374
57532
|
const laneKey = this.sessionActiveLanes.get(sessionKey);
|
|
@@ -57844,14 +58002,14 @@ var ParallAgentGateway = class {
|
|
|
57844
58002
|
this.writeContextFile(laneContextFilePath2, contextBody);
|
|
57845
58003
|
}
|
|
57846
58004
|
this.inFlightDispatches++;
|
|
57847
|
-
const
|
|
57848
|
-
this.opts.log?.warn(`dispatch deadline exceeded (${this.DISPATCH_DEADLINE_MS}ms) for ${event.messageId} on ${sessionKey}; aborting`);
|
|
58005
|
+
const dispatchDeadline = this.dispatchInactivityDeadlines.start(sessionKey, this.DISPATCH_DEADLINE_MS, () => {
|
|
58006
|
+
this.opts.log?.warn(`dispatch inactivity deadline exceeded (${this.DISPATCH_DEADLINE_MS}ms) for ${event.messageId} on ${sessionKey}; aborting`);
|
|
57849
58007
|
try {
|
|
57850
58008
|
this.opts.dispatchAdapter.abortDispatch?.(sessionKey);
|
|
57851
58009
|
} catch (err) {
|
|
57852
58010
|
this.opts.log?.warn(`abortDispatch threw for ${sessionKey}: ${String(err)}`);
|
|
57853
58011
|
}
|
|
57854
|
-
}
|
|
58012
|
+
});
|
|
57855
58013
|
let binding = this.sessionBindings.get(sessionKey);
|
|
57856
58014
|
let inputStepsCreated = false;
|
|
57857
58015
|
let turnHandle;
|
|
@@ -57872,8 +58030,10 @@ var ParallAgentGateway = class {
|
|
|
57872
58030
|
bodyForAgent,
|
|
57873
58031
|
sessionKey,
|
|
57874
58032
|
context: dispatchContext,
|
|
57875
|
-
inputLifecycle
|
|
58033
|
+
inputLifecycle,
|
|
58034
|
+
noteActivity: dispatchDeadline.touch
|
|
57876
58035
|
})) {
|
|
58036
|
+
dispatchDeadline.touch();
|
|
57877
58037
|
if (runtimeEvent.type === "runtime_session") {
|
|
57878
58038
|
const priorAgentSessionId = binding?.agentSessionId;
|
|
57879
58039
|
binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2);
|
|
@@ -58010,8 +58170,7 @@ var ParallAgentGateway = class {
|
|
|
58010
58170
|
}
|
|
58011
58171
|
throw err;
|
|
58012
58172
|
} finally {
|
|
58013
|
-
|
|
58014
|
-
clearTimeout(deadlineTimer);
|
|
58173
|
+
dispatchDeadline.dispose();
|
|
58015
58174
|
const metricsSnapshot = getDispatchMetrics(sessionKey);
|
|
58016
58175
|
const durationMs = metricsSnapshot ? Date.now() - metricsSnapshot.started_at : 0;
|
|
58017
58176
|
const effectiveOutcome = turnOutcomeEvent?.outcome ?? (dispatchError || sawErrorEvent ? "runtime_crash" : "ok");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/parall",
|
|
3
|
-
"version": "1.55.
|
|
3
|
+
"version": "1.55.2",
|
|
4
4
|
"description": "OpenClaw channel plugin for Parall IM",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"openclaw.plugin.json"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@parall/agent-core": "1.55.
|
|
20
|
-
"@parall/sdk": "1.55.
|
|
19
|
+
"@parall/agent-core": "1.55.2",
|
|
20
|
+
"@parall/sdk": "1.55.2"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/node": "^22.0.0",
|
|
@@ -114,6 +114,7 @@ function parseTweets(data) { /* ... */ }
|
|
|
114
114
|
- `tab.cookie(name)` · `tab.fetch(url, opts)` (in-browser fetch, carries the session)
|
|
115
115
|
- `tab.eval(expr)` (escape hatch) · `tab.click(sel)` · `tab.fill(sel, text)` · `tab.navigate(url)`
|
|
116
116
|
- `tab.waitForSelector(sel)` · `tab.getTitle()` · `tab.getURL()` · `tab.screenshot()` · `tab.close()`
|
|
117
|
+
- `tab.setFileInput(sel, url, opts?)` — upload a file into an `<input type=file>`
|
|
117
118
|
- `fetch` — runtime-side HTTP, does NOT go through the browser (no session)
|
|
118
119
|
- `console` — logs · `args` — the invocation input
|
|
119
120
|
|
|
@@ -122,6 +123,40 @@ returns structured data; eval is the last resort. Always `tab.close()` what you
|
|
|
122
123
|
open, and do it in a `finally` — an early return or a thrown fetch is exactly
|
|
123
124
|
when the tab leaks.
|
|
124
125
|
|
|
126
|
+
### Uploading a file
|
|
127
|
+
|
|
128
|
+
`tab.setFileInput` takes a URL, never a path, and the runtime — not the page —
|
|
129
|
+
fetches the bytes. That is what makes it work where an in-page `fetch` +
|
|
130
|
+
`DataTransfer` cannot: upload targets ship a Content-Security-Policy that
|
|
131
|
+
forbids the page from fetching an arbitrary file host (Instagram's `default-src`
|
|
132
|
+
allows only its own domains), and you cannot change a header on their site. The
|
|
133
|
+
runtime is not a page, so no CSP applies to it — and a large video never has to
|
|
134
|
+
pass through the page's memory.
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
await tab.setFileInput("input[type=file]", videoUrl, { filename: "clip.mp4" });
|
|
138
|
+
await tab.click("button[type=submit]");
|
|
139
|
+
await tab.waitForSelector(".upload-complete"); // ← do not skip this
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Four rules that decide whether your clip works:
|
|
143
|
+
|
|
144
|
+
1. **The URL must be fetchable with no credentials** — a public direct link or a
|
|
145
|
+
signed temporary one. The runtime sends no cookies, so a Drive or Feishu link
|
|
146
|
+
copied from the address bar will not work: those are HTML pages behind a
|
|
147
|
+
login. If the file needs a session, use a logged-in tab to obtain a signed
|
|
148
|
+
direct link first, then pass THAT here. An HTML answer is refused with
|
|
149
|
+
`EDGE_FILE_SOURCE_NOT_A_FILE` rather than uploaded as if it were a file.
|
|
150
|
+
2. **Finish the upload inside the same command.** The browser reads the file when
|
|
151
|
+
the page submits, and the runtime deletes it when your command ends. Injecting
|
|
152
|
+
and returning immediately uploads nothing — wait for the site to confirm.
|
|
153
|
+
3. **Main-document inputs only.** An input inside an iframe or a shadow root is
|
|
154
|
+
not addressable and returns `EDGE_FILE_SELECTOR_MISS`. If the page rebuilds
|
|
155
|
+
the input after you inject (navigation, re-render), inject again.
|
|
156
|
+
4. **`opts.filename` is what the page sees**, and many sites validate by
|
|
157
|
+
extension — set it when the URL has none. `opts.timeoutMs` bounds the
|
|
158
|
+
download. One file per call; there is no multi-file form yet.
|
|
159
|
+
|
|
125
160
|
## Develop → publish → iterate
|
|
126
161
|
|
|
127
162
|
Use the platform `parall clip` subcommands — they reuse the credentials you
|
|
@@ -228,18 +263,30 @@ and the folder is just a manifest:
|
|
|
228
263
|
}
|
|
229
264
|
```
|
|
230
265
|
|
|
231
|
-
- The `mcp` block takes ONLY `server_url
|
|
232
|
-
"
|
|
233
|
-
|
|
234
|
-
definition.
|
|
266
|
+
- The `mcp` block takes ONLY `server_url`, `auth` (`"none" | "api_key" |
|
|
267
|
+
"basic" | "oauth"`, legacy `"bearer"` accepted) and `auth_headers`. Any
|
|
268
|
+
other key is refused at publish — a credential belongs to the installing
|
|
269
|
+
org's own configuration, NEVER to the clip definition.
|
|
270
|
+
- `auth` is REQUIRED at publish: you know what your server speaks, and this
|
|
271
|
+
one word decides what the install form asks for (`none` = zero input,
|
|
272
|
+
`api_key` = key field(s), `basic` = username + password, `oauth` = a
|
|
273
|
+
Connect button).
|
|
274
|
+
- `api_key` delivers as a single `X-API-Key` header by default. When the
|
|
275
|
+
server wants a different shape, declare `auth_headers` (max 4 slots, one
|
|
276
|
+
admin-supplied value each): `[{"name": "Authorization", "scheme":
|
|
277
|
+
"Bearer"}]` for Bearer tokens, `[{"name": "api-key"}]` for a custom
|
|
278
|
+
header, or a pair like `[{"name": "CF-Access-Client-Id"}, {"name":
|
|
279
|
+
"CF-Access-Client-Secret"}]`. Framing/platform headers (Host, Cookie,
|
|
280
|
+
X-Prll-*, …) are refused.
|
|
235
281
|
- `server_url` must be an absolute **https** URL with no embedded credentials,
|
|
236
282
|
query, or fragment. It is review material, frozen with the approved version.
|
|
283
|
+
It stays OPTIONAL for self-hosted products where each org connects its own
|
|
284
|
+
instance URL.
|
|
237
285
|
- Do NOT put the server in the top-level `server` / `auth` manifest keys —
|
|
238
286
|
those are legacy Edge-manifest fields nothing reads. Only the `mcp` block
|
|
239
287
|
declares the server.
|
|
240
|
-
-
|
|
241
|
-
|
|
242
|
-
republishing.
|
|
288
|
+
- What you declare is LOCKED: the installing org's config must match it, and
|
|
289
|
+
changing the URL, auth mode, or header shape means republishing.
|
|
243
290
|
- Entering the credential / completing OAuth is a HUMAN step in the Clip
|
|
244
291
|
Console (the config-write endpoints are session-only — an API key cannot
|
|
245
292
|
call them). An org can add SEVERAL connections to one MCP clip — one
|