impel-cli 0.15.2 → 0.15.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/package.json +1 -1
- package/src/agents.js +9 -1
- package/src/skills.js +40 -12
package/package.json
CHANGED
package/src/agents.js
CHANGED
|
@@ -29,6 +29,7 @@ const NATIVE_AGENT_TOOL_NAMES = [
|
|
|
29
29
|
const SAFE_AGENT_ID_RE = /^[A-Za-z0-9_.:-]{1,160}$/u;
|
|
30
30
|
const SAFE_SCOPE_PARAM_RE = /^[A-Za-z0-9_.:-]{1,160}$/u;
|
|
31
31
|
const MAX_CATALOG_ITEMS = 500;
|
|
32
|
+
const RETRYABLE_AGENT_CATALOG_ERROR = /request timed out|fetch failed|ECONNRESET|ETIMEDOUT|EAI_AGAIN|network error/iu;
|
|
32
33
|
const RESERVED_AGENT_NAMES = Object.freeze({
|
|
33
34
|
claude: new Set(["explore", "general-purpose", "plan"]),
|
|
34
35
|
codex: new Set(["default", "explorer", "worker"]),
|
|
@@ -540,7 +541,14 @@ export async function syncAgentProfiles({
|
|
|
540
541
|
.map((profile) => ({ ...profile, skipped: true, reason: "fresh" }));
|
|
541
542
|
if (pending.length === 0) return skipped;
|
|
542
543
|
|
|
543
|
-
|
|
544
|
+
let catalog;
|
|
545
|
+
try {
|
|
546
|
+
catalog = await fetchCatalog({ gatewayUrl, credential, tenantId: normalizedTenant });
|
|
547
|
+
} catch (error) {
|
|
548
|
+
if (!RETRYABLE_AGENT_CATALOG_ERROR.test(String(error?.message || error))) throw error;
|
|
549
|
+
logger.log("Agents: catalog request failed transiently; retrying once…");
|
|
550
|
+
catalog = await fetchCatalog({ gatewayUrl, credential, tenantId: normalizedTenant });
|
|
551
|
+
}
|
|
544
552
|
const results = [];
|
|
545
553
|
for (const profile of pending) {
|
|
546
554
|
const result = syncAgentProfile({
|
package/src/skills.js
CHANGED
|
@@ -34,6 +34,7 @@ export const SKILLS_FALLBACK_GATEWAY_URL = "https://gateway.useimpel.ai";
|
|
|
34
34
|
// and Claude both exit non-zero when a marketplace/plugin is already present, so
|
|
35
35
|
// these must not be reported as real failures.
|
|
36
36
|
const BENIGN_OUTPUT = /already (exist|install|add|present|configur)|up[ -]?to[ -]?date|no changes|nothing to (do|update)/i;
|
|
37
|
+
const TRANSIENT_SKILL_OUTPUT = /timed? out|timeout of \d+ms exceeded|ECONNRESET|ETIMEDOUT|EAI_AGAIN|network error|failed to download/i;
|
|
37
38
|
|
|
38
39
|
const CLIENT_SPECS = {
|
|
39
40
|
claude: {
|
|
@@ -74,20 +75,24 @@ export function resolveMarketplaceName(marketplace) {
|
|
|
74
75
|
return typeof name === "string" && name.trim() ? name.trim() : null;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
/** Resolve a marketplace name from Claude
|
|
78
|
+
/** Resolve a marketplace name from Claude or Codex `marketplace list --json`. */
|
|
78
79
|
export function resolveConfiguredMarketplaceName(output, sourceUrl) {
|
|
79
|
-
let
|
|
80
|
+
let payload;
|
|
80
81
|
try {
|
|
81
|
-
|
|
82
|
+
payload = JSON.parse(String(output || ""));
|
|
82
83
|
} catch {
|
|
83
84
|
return null;
|
|
84
85
|
}
|
|
86
|
+
const marketplaces = Array.isArray(payload)
|
|
87
|
+
? payload
|
|
88
|
+
: payload?.marketplaces;
|
|
85
89
|
if (!Array.isArray(marketplaces)) return null;
|
|
86
90
|
const normalizedSource = String(sourceUrl || "").replace(/\/+$/u, "");
|
|
87
|
-
const marketplace = marketplaces.find((candidate) =>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
const marketplace = marketplaces.find((candidate) => {
|
|
92
|
+
const source = candidate?.url || candidate?.marketplaceSource?.source;
|
|
93
|
+
return typeof source === "string"
|
|
94
|
+
&& source.replace(/\/+$/u, "") === normalizedSource;
|
|
95
|
+
});
|
|
91
96
|
return resolveMarketplaceName(marketplace);
|
|
92
97
|
}
|
|
93
98
|
|
|
@@ -275,6 +280,18 @@ function isBenign(result) {
|
|
|
275
280
|
return result.ok || BENIGN_OUTPUT.test(`${result.stdout}\n${result.stderr}`);
|
|
276
281
|
}
|
|
277
282
|
|
|
283
|
+
async function runSkillCommandWithRetry(run, bin, args, env) {
|
|
284
|
+
const first = await run(bin, args, env);
|
|
285
|
+
if (
|
|
286
|
+
first.ok
|
|
287
|
+
|| first.missing
|
|
288
|
+
|| !TRANSIENT_SKILL_OUTPUT.test(`${first.stdout}\n${first.stderr}`)
|
|
289
|
+
) {
|
|
290
|
+
return first;
|
|
291
|
+
}
|
|
292
|
+
return run(bin, args, env);
|
|
293
|
+
}
|
|
294
|
+
|
|
278
295
|
/** Fetch the marketplace.json and return its registered name, or null on any failure. */
|
|
279
296
|
export async function fetchMarketplaceName(url, fetchImpl = fetch) {
|
|
280
297
|
const controller = new AbortController();
|
|
@@ -343,7 +360,12 @@ export async function syncSkills({
|
|
|
343
360
|
marketplaceSourceUrl: sourceUrl,
|
|
344
361
|
marketplaceName,
|
|
345
362
|
})[0];
|
|
346
|
-
const registerResult = await
|
|
363
|
+
const registerResult = await runSkillCommandWithRetry(
|
|
364
|
+
run,
|
|
365
|
+
spec.bin,
|
|
366
|
+
registerCommand.args,
|
|
367
|
+
env,
|
|
368
|
+
);
|
|
347
369
|
if (registerResult.missing) {
|
|
348
370
|
failures.push({ phase: registerCommand.phase, reason: `\`${spec.bin}\` disappeared mid-sync` });
|
|
349
371
|
} else if (!isBenign(registerResult)) {
|
|
@@ -354,9 +376,10 @@ export async function syncSkills({
|
|
|
354
376
|
}
|
|
355
377
|
|
|
356
378
|
// The public manifest fetch is deliberately best-effort and can time out.
|
|
357
|
-
// Once
|
|
358
|
-
// the same dynamic name so refreshes can still use
|
|
359
|
-
|
|
379
|
+
// Once either client has registered the marketplace, its local JSON index
|
|
380
|
+
// gives us the same dynamic name so refreshes can still use
|
|
381
|
+
// PLUGIN@MARKETPLACE.
|
|
382
|
+
if (!marketplaceName && !registerResult.missing) {
|
|
360
383
|
const listed = await run(spec.bin, ["plugin", "marketplace", "list", "--json"], env);
|
|
361
384
|
if (listed.ok) marketplaceName = resolveConfiguredMarketplaceName(listed.stdout, sourceUrl);
|
|
362
385
|
}
|
|
@@ -367,7 +390,12 @@ export async function syncSkills({
|
|
|
367
390
|
marketplaceName,
|
|
368
391
|
}).slice(1);
|
|
369
392
|
for (const command of commands) {
|
|
370
|
-
const result = await
|
|
393
|
+
const result = await runSkillCommandWithRetry(
|
|
394
|
+
run,
|
|
395
|
+
spec.bin,
|
|
396
|
+
command.args,
|
|
397
|
+
env,
|
|
398
|
+
);
|
|
371
399
|
if (result.missing) {
|
|
372
400
|
failures.push({ phase: command.phase, reason: `\`${spec.bin}\` disappeared mid-sync` });
|
|
373
401
|
break;
|