@agenshield/daemon 0.7.0 → 0.7.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/index.js CHANGED
@@ -1046,10 +1046,7 @@ PLISTEOF`);
1046
1046
  }
1047
1047
  async function startOpenClawServices() {
1048
1048
  try {
1049
- try {
1050
- await execAsync3(`sudo launchctl kickstart system/${OPENCLAW_GATEWAY_LABEL}`);
1051
- } catch {
1052
- }
1049
+ await execAsync3(`sudo launchctl kickstart system/${OPENCLAW_GATEWAY_LABEL}`);
1053
1050
  return {
1054
1051
  success: true,
1055
1052
  message: "OpenClaw gateway started"
@@ -1064,10 +1061,7 @@ async function startOpenClawServices() {
1064
1061
  }
1065
1062
  async function stopOpenClawServices() {
1066
1063
  try {
1067
- try {
1068
- await execAsync3(`sudo launchctl kill SIGTERM system/${OPENCLAW_GATEWAY_LABEL}`);
1069
- } catch {
1070
- }
1064
+ await execAsync3(`sudo launchctl kill SIGTERM system/${OPENCLAW_GATEWAY_LABEL}`);
1071
1065
  return {
1072
1066
  success: true,
1073
1067
  message: "OpenClaw gateway stopped"
@@ -1082,10 +1076,7 @@ async function stopOpenClawServices() {
1082
1076
  }
1083
1077
  async function restartOpenClawServices() {
1084
1078
  try {
1085
- try {
1086
- await execAsync3(`sudo launchctl kickstart -k system/${OPENCLAW_GATEWAY_LABEL}`);
1087
- } catch {
1088
- }
1079
+ await execAsync3(`sudo launchctl kickstart -k system/${OPENCLAW_GATEWAY_LABEL}`);
1089
1080
  return {
1090
1081
  success: true,
1091
1082
  message: "OpenClaw gateway restarted"
@@ -1168,15 +1159,29 @@ function getOpenClawStatusSync() {
1168
1159
  async function getOpenClawDashboardUrl() {
1169
1160
  try {
1170
1161
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
1171
- const agentUsername = path22.basename(agentHome);
1172
- const { stdout, stderr } = await execAsync3(
1173
- `sudo -H -u ${agentUsername} ${OPENCLAW_LAUNCHER_PATH} dashboard`,
1174
- { timeout: 15e3, maxBuffer: 10 * 1024 * 1024 }
1175
- );
1176
- const url = stdout.trim();
1177
- if (!url) {
1178
- return { success: false, error: stderr.trim() || "No URL returned from openclaw dashboard" };
1162
+ const configPath = path22.join(agentHome, ".openclaw", "openclaw.json");
1163
+ let raw;
1164
+ try {
1165
+ raw = await fs32.readFile(configPath, "utf-8");
1166
+ } catch (err) {
1167
+ if (err.code === "EACCES") {
1168
+ const agentUsername = path22.basename(agentHome);
1169
+ const { stdout } = await execAsync3(
1170
+ `sudo -H -u ${agentUsername} cat "${configPath}"`,
1171
+ { cwd: "/" }
1172
+ );
1173
+ raw = stdout;
1174
+ } else {
1175
+ return { success: false, error: `Cannot read openclaw.json: ${err.message}` };
1176
+ }
1177
+ }
1178
+ const config = JSON.parse(raw);
1179
+ const port = config.gateway?.port;
1180
+ const token = config.gateway?.auth?.token;
1181
+ if (!port || !token) {
1182
+ return { success: false, error: "Gateway port or auth token not found in openclaw.json" };
1179
1183
  }
1184
+ const url = `http://127.0.0.1:${port}/?token=${token}`;
1180
1185
  return { success: true, url };
1181
1186
  } catch (error) {
1182
1187
  return { success: false, error: `Failed to get dashboard URL: ${error.message}` };
@@ -1290,6 +1295,41 @@ var init_crypto = __esm({
1290
1295
  }
1291
1296
  });
1292
1297
 
1298
+ // libs/shield-daemon/src/vault/installation-key.ts
1299
+ import * as crypto2 from "node:crypto";
1300
+ async function getInstallationKey() {
1301
+ if (cachedKey) return cachedKey;
1302
+ const vault = getVault();
1303
+ const contents = await vault.load();
1304
+ if (contents.installationKey) {
1305
+ cachedKey = contents.installationKey;
1306
+ return cachedKey;
1307
+ }
1308
+ const key = crypto2.randomBytes(32).toString("hex");
1309
+ await vault.set("installationKey", key);
1310
+ cachedKey = key;
1311
+ console.log("[InstallationKey] Generated new installation key");
1312
+ return key;
1313
+ }
1314
+ async function getInstallationTag() {
1315
+ const key = await getInstallationKey();
1316
+ return `${INSTALLATION_KEY_PREFIX}${key}`;
1317
+ }
1318
+ function hasValidInstallationTagSync(tags) {
1319
+ if (!cachedKey) return false;
1320
+ const fullTag = `${INSTALLATION_KEY_PREFIX}${cachedKey}`;
1321
+ return tags.some((tag) => tag === fullTag);
1322
+ }
1323
+ var INSTALLATION_KEY_PREFIX, cachedKey;
1324
+ var init_installation_key = __esm({
1325
+ "libs/shield-daemon/src/vault/installation-key.ts"() {
1326
+ "use strict";
1327
+ init_vault();
1328
+ INSTALLATION_KEY_PREFIX = "agenshield-";
1329
+ cachedKey = null;
1330
+ }
1331
+ });
1332
+
1293
1333
  // libs/shield-daemon/src/vault/index.ts
1294
1334
  import * as fs4 from "node:fs";
1295
1335
  import * as path4 from "node:path";
@@ -1310,6 +1350,7 @@ var init_vault = __esm({
1310
1350
  init_crypto();
1311
1351
  init_paths();
1312
1352
  init_crypto();
1353
+ init_installation_key();
1313
1354
  Vault = class {
1314
1355
  key;
1315
1356
  vaultPath;
@@ -1766,7 +1807,7 @@ var init_secret_sync = __esm({
1766
1807
  });
1767
1808
 
1768
1809
  // libs/shield-daemon/src/server.ts
1769
- import * as fs21 from "node:fs";
1810
+ import * as fs23 from "node:fs";
1770
1811
  import Fastify from "fastify";
1771
1812
  import cors from "@fastify/cors";
1772
1813
  import fastifyStatic from "@fastify/static";
@@ -1844,7 +1885,7 @@ init_state();
1844
1885
  init_vault();
1845
1886
 
1846
1887
  // libs/shield-daemon/src/auth/session.ts
1847
- import * as crypto2 from "node:crypto";
1888
+ import * as crypto3 from "node:crypto";
1848
1889
  import { DEFAULT_AUTH_CONFIG } from "@agenshield/ipc";
1849
1890
  var SessionManager = class {
1850
1891
  sessions = /* @__PURE__ */ new Map();
@@ -1858,7 +1899,7 @@ var SessionManager = class {
1858
1899
  * Generate a secure random token
1859
1900
  */
1860
1901
  generateToken() {
1861
- return crypto2.randomBytes(32).toString("base64url");
1902
+ return crypto3.randomBytes(32).toString("base64url");
1862
1903
  }
1863
1904
  /**
1864
1905
  * Create a new session
@@ -2150,11 +2191,24 @@ function getOpenClawConfigPath() {
2150
2191
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
2151
2192
  return path8.join(agentHome, ".openclaw", "openclaw.json");
2152
2193
  }
2153
- function readConfig() {
2194
+ function readOpenClawConfig() {
2154
2195
  const configPath = getOpenClawConfigPath();
2155
2196
  try {
2156
2197
  if (fs8.existsSync(configPath)) {
2157
- return JSON.parse(fs8.readFileSync(configPath, "utf-8"));
2198
+ try {
2199
+ return JSON.parse(fs8.readFileSync(configPath, "utf-8"));
2200
+ } catch (err) {
2201
+ if (err.code === "EACCES") {
2202
+ const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
2203
+ const agentUsername = path8.basename(agentHome);
2204
+ const raw = execSync6(
2205
+ `sudo -H -u ${agentUsername} cat "${configPath}"`,
2206
+ { encoding: "utf-8", cwd: "/", stdio: ["pipe", "pipe", "pipe"] }
2207
+ );
2208
+ return JSON.parse(raw);
2209
+ }
2210
+ throw err;
2211
+ }
2158
2212
  }
2159
2213
  } catch {
2160
2214
  console.warn("[OpenClawConfig] Failed to read openclaw.json, starting fresh");
@@ -2172,7 +2226,7 @@ function writeConfig(config) {
2172
2226
  const agentUsername = path8.basename(agentHome);
2173
2227
  execSync6(
2174
2228
  `sudo -H -u ${agentUsername} tee "${configPath}" > /dev/null`,
2175
- { input: JSON.stringify(config, null, 2), stdio: ["pipe", "pipe", "pipe"] }
2229
+ { input: JSON.stringify(config, null, 2), stdio: ["pipe", "pipe", "pipe"], cwd: "/" }
2176
2230
  );
2177
2231
  } else {
2178
2232
  throw err;
@@ -2180,27 +2234,30 @@ function writeConfig(config) {
2180
2234
  }
2181
2235
  }
2182
2236
  function addSkillEntry(slug) {
2183
- const config = readConfig();
2237
+ const config = readOpenClawConfig();
2184
2238
  if (!config.skills) {
2185
2239
  config.skills = {};
2186
2240
  }
2187
2241
  if (!config.skills.entries) {
2188
2242
  config.skills.entries = {};
2189
2243
  }
2190
- config.skills.entries[slug] = { enabled: true };
2244
+ const existing = config.skills.entries[slug] ?? {};
2245
+ config.skills.entries[slug] = { ...existing, enabled: true };
2191
2246
  writeConfig(config);
2192
2247
  console.log(`[OpenClawConfig] Added skill entry: ${slug}`);
2193
2248
  }
2194
2249
  function removeSkillEntry(slug) {
2195
- const config = readConfig();
2250
+ const config = readOpenClawConfig();
2196
2251
  if (config.skills?.entries?.[slug]) {
2197
- delete config.skills.entries[slug];
2252
+ const existing = config.skills.entries[slug];
2253
+ delete existing.env;
2254
+ config.skills.entries[slug] = { ...existing, enabled: false };
2198
2255
  writeConfig(config);
2199
- console.log(`[OpenClawConfig] Removed skill entry: ${slug}`);
2256
+ console.log(`[OpenClawConfig] Disabled skill entry: ${slug}`);
2200
2257
  }
2201
2258
  }
2202
2259
  function syncOpenClawFromPolicies(policies) {
2203
- const config = readConfig();
2260
+ const config = readOpenClawConfig();
2204
2261
  if (!config.skills) config.skills = {};
2205
2262
  const allowBundled = [];
2206
2263
  for (const p of policies) {
@@ -2419,7 +2476,7 @@ function generatePolicyMarkdown(policies, knownSkills) {
2419
2476
  // libs/shield-daemon/src/watchers/skills.ts
2420
2477
  import * as fs11 from "node:fs";
2421
2478
  import * as path11 from "node:path";
2422
- import * as crypto3 from "node:crypto";
2479
+ import * as crypto4 from "node:crypto";
2423
2480
  import { parseSkillMd } from "@agenshield/sandbox";
2424
2481
 
2425
2482
  // libs/shield-daemon/src/services/marketplace.ts
@@ -2448,35 +2505,35 @@ var ANALYSIS_TIMEOUT = 4 * 6e4;
2448
2505
  var SEARCH_CACHE_TTL = 6e4;
2449
2506
  var DETAIL_CACHE_TTL = 5 * 6e4;
2450
2507
  var SHORT_TIMEOUT = 1e4;
2451
- async function convexAction(path21, args, timeout) {
2508
+ async function convexAction(path24, args, timeout) {
2452
2509
  const res = await fetch(`${CONVEX_BASE}/api/action`, {
2453
2510
  method: "POST",
2454
2511
  signal: AbortSignal.timeout(timeout),
2455
2512
  headers: { "Content-Type": "application/json" },
2456
- body: JSON.stringify({ path: path21, args, format: "json" })
2513
+ body: JSON.stringify({ path: path24, args, format: "json" })
2457
2514
  });
2458
2515
  if (!res.ok) {
2459
- throw new Error(`Convex action ${path21} returned ${res.status}`);
2516
+ throw new Error(`Convex action ${path24} returned ${res.status}`);
2460
2517
  }
2461
2518
  const body = await res.json();
2462
2519
  if (body.status === "error") {
2463
- throw new Error(`Convex action ${path21}: ${body.errorMessage ?? "unknown error"}`);
2520
+ throw new Error(`Convex action ${path24}: ${body.errorMessage ?? "unknown error"}`);
2464
2521
  }
2465
2522
  return body.value;
2466
2523
  }
2467
- async function convexQuery(path21, args, timeout) {
2524
+ async function convexQuery(path24, args, timeout) {
2468
2525
  const res = await fetch(`${CONVEX_BASE}/api/query`, {
2469
2526
  method: "POST",
2470
2527
  signal: AbortSignal.timeout(timeout),
2471
2528
  headers: { "Content-Type": "application/json" },
2472
- body: JSON.stringify({ path: path21, args, format: "json" })
2529
+ body: JSON.stringify({ path: path24, args, format: "json" })
2473
2530
  });
2474
2531
  if (!res.ok) {
2475
- throw new Error(`Convex query ${path21} returned ${res.status}`);
2532
+ throw new Error(`Convex query ${path24} returned ${res.status}`);
2476
2533
  }
2477
2534
  const body = await res.json();
2478
2535
  if (body.status === "error") {
2479
- throw new Error(`Convex query ${path21}: ${body.errorMessage ?? "unknown error"}`);
2536
+ throw new Error(`Convex query ${path24}: ${body.errorMessage ?? "unknown error"}`);
2480
2537
  }
2481
2538
  return body.value;
2482
2539
  }
@@ -2608,6 +2665,17 @@ function updateDownloadedAnalysis(slug, analysis) {
2608
2665
  } catch {
2609
2666
  }
2610
2667
  }
2668
+ function markDownloadedAsInstalled(slug) {
2669
+ const metaPath = path9.join(getMarketplaceDir(), slug, "metadata.json");
2670
+ try {
2671
+ if (!fs9.existsSync(metaPath)) return;
2672
+ const meta = JSON.parse(fs9.readFileSync(metaPath, "utf-8"));
2673
+ if (meta.wasInstalled) return;
2674
+ meta.wasInstalled = true;
2675
+ fs9.writeFileSync(metaPath, JSON.stringify(meta, null, 2), "utf-8");
2676
+ } catch {
2677
+ }
2678
+ }
2611
2679
  function listDownloadedSkills() {
2612
2680
  const baseDir = getMarketplaceDir();
2613
2681
  if (!fs9.existsSync(baseDir)) return [];
@@ -2628,7 +2696,8 @@ function listDownloadedSkills() {
2628
2696
  tags: meta.tags ?? [],
2629
2697
  hasAnalysis: !!meta.analysis,
2630
2698
  source: meta.source,
2631
- analysis: meta.analysis
2699
+ analysis: meta.analysis,
2700
+ wasInstalled: meta.wasInstalled ?? false
2632
2701
  });
2633
2702
  } catch {
2634
2703
  }
@@ -2678,9 +2747,9 @@ function inlineImagesInMarkdown(markdown, files) {
2678
2747
  const mime = isImageExt(file.name);
2679
2748
  if (mime && file.content.startsWith("data:")) {
2680
2749
  imageMap.set(file.name, file.content);
2681
- const basename3 = file.name.split("/").pop() ?? "";
2682
- if (basename3 && !imageMap.has(basename3)) {
2683
- imageMap.set(basename3, file.content);
2750
+ const basename6 = file.name.split("/").pop() ?? "";
2751
+ if (basename6 && !imageMap.has(basename6)) {
2752
+ imageMap.set(basename6, file.content);
2684
2753
  }
2685
2754
  }
2686
2755
  }
@@ -3206,10 +3275,10 @@ function emitSecurityWarning(warning) {
3206
3275
  function emitSecurityCritical(issue) {
3207
3276
  daemonEvents.broadcast("security:critical", { message: issue });
3208
3277
  }
3209
- function emitApiRequest(method, path21, statusCode, duration, requestBody, responseBody) {
3278
+ function emitApiRequest(method, path24, statusCode, duration, requestBody, responseBody) {
3210
3279
  daemonEvents.broadcast("api:request", {
3211
3280
  method,
3212
- path: path21,
3281
+ path: path24,
3213
3282
  statusCode,
3214
3283
  duration,
3215
3284
  ...requestBody !== void 0 && { requestBody },
@@ -3225,6 +3294,9 @@ function emitSkillUntrustedDetected(name, reason) {
3225
3294
  function emitSkillApproved(skillName) {
3226
3295
  daemonEvents.broadcast("skills:approved", { name: skillName });
3227
3296
  }
3297
+ function emitExecDenied(command, reason) {
3298
+ daemonEvents.broadcast("exec:denied", { command, reason });
3299
+ }
3228
3300
  function emitAgenCoAuthRequired(authUrl, integration) {
3229
3301
  daemonEvents.broadcast("agenco:auth_required", { authUrl, integration });
3230
3302
  }
@@ -3264,6 +3336,56 @@ function emitEvent(type, data) {
3264
3336
 
3265
3337
  // libs/shield-daemon/src/watchers/skills.ts
3266
3338
  init_paths();
3339
+
3340
+ // libs/shield-daemon/src/services/skill-tag-injector.ts
3341
+ init_installation_key();
3342
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
3343
+ var FRONTMATTER_RE = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
3344
+ async function injectInstallationTag(content) {
3345
+ const tag = await getInstallationTag();
3346
+ const match = content.match(FRONTMATTER_RE);
3347
+ if (!match) {
3348
+ return `---
3349
+ tags:
3350
+ - ${tag}
3351
+ ---
3352
+ ${content}`;
3353
+ }
3354
+ try {
3355
+ const metadata = parseYaml(match[1]);
3356
+ if (!metadata || typeof metadata !== "object") {
3357
+ return content;
3358
+ }
3359
+ if (!Array.isArray(metadata.tags)) {
3360
+ metadata.tags = [];
3361
+ }
3362
+ metadata.tags = metadata.tags.filter(
3363
+ (t) => typeof t !== "string" || !t.startsWith("agenshield-")
3364
+ );
3365
+ metadata.tags.push(tag);
3366
+ return `---
3367
+ ${stringifyYaml(metadata).trimEnd()}
3368
+ ---
3369
+ ${match[2]}`;
3370
+ } catch {
3371
+ return content;
3372
+ }
3373
+ }
3374
+ function extractTagsFromSkillMd(content) {
3375
+ const match = content.match(FRONTMATTER_RE);
3376
+ if (!match) return [];
3377
+ try {
3378
+ const metadata = parseYaml(match[1]);
3379
+ if (!metadata || typeof metadata !== "object") return [];
3380
+ if (!Array.isArray(metadata.tags)) return [];
3381
+ return metadata.tags.filter((t) => typeof t === "string");
3382
+ } catch {
3383
+ return [];
3384
+ }
3385
+ }
3386
+
3387
+ // libs/shield-daemon/src/watchers/skills.ts
3388
+ init_installation_key();
3267
3389
  function getApprovedSkillsPath() {
3268
3390
  return path11.join(getSystemConfigDir(), "approved-skills.json");
3269
3391
  }
@@ -3402,7 +3524,7 @@ function computeSkillHash(skillDir) {
3402
3524
  const files = readSkillFiles(skillDir);
3403
3525
  if (files.length === 0) return null;
3404
3526
  files.sort((a, b) => a.name.localeCompare(b.name));
3405
- const hash = crypto3.createHash("sha256");
3527
+ const hash = crypto4.createHash("sha256");
3406
3528
  for (const file of files) {
3407
3529
  hash.update(file.name);
3408
3530
  hash.update(file.content);
@@ -3431,10 +3553,33 @@ function scanSkills() {
3431
3553
  const approvedEntry = approvedMap.get(skillName);
3432
3554
  if (!approvedEntry) {
3433
3555
  const fullPath = path11.join(skillsDir, skillName);
3434
- const slug = moveToMarketplace(skillName, fullPath);
3435
- if (slug) {
3436
- if (callbacks.onUntrustedDetected) {
3437
- callbacks.onUntrustedDetected({ name: skillName, reason: "Skill not in approved list" });
3556
+ let autoApproved = false;
3557
+ for (const mdName of ["SKILL.md", "skill.md"]) {
3558
+ const mdPath = path11.join(fullPath, mdName);
3559
+ try {
3560
+ if (fs11.existsSync(mdPath)) {
3561
+ const content = fs11.readFileSync(mdPath, "utf-8");
3562
+ const tags = extractTagsFromSkillMd(content);
3563
+ if (hasValidInstallationTagSync(tags)) {
3564
+ console.log(`[SkillsWatcher] Auto-approving skill with valid installation tag: ${skillName}`);
3565
+ const hash = computeSkillHash(fullPath);
3566
+ addToApprovedList(skillName, void 0, hash ?? void 0);
3567
+ if (callbacks.onApproved) {
3568
+ callbacks.onApproved(skillName);
3569
+ }
3570
+ autoApproved = true;
3571
+ break;
3572
+ }
3573
+ }
3574
+ } catch {
3575
+ }
3576
+ }
3577
+ if (!autoApproved) {
3578
+ const slug = moveToMarketplace(skillName, fullPath);
3579
+ if (slug) {
3580
+ if (callbacks.onUntrustedDetected) {
3581
+ callbacks.onUntrustedDetected({ name: skillName, reason: "Skill not in approved list" });
3582
+ }
3438
3583
  }
3439
3584
  }
3440
3585
  } else if (approvedEntry.hash) {
@@ -3464,26 +3609,16 @@ function scanSkills() {
3464
3609
  }
3465
3610
  function detectOpenClawMismatches() {
3466
3611
  try {
3467
- const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
3468
- const configPath = path11.join(agentHome, ".openclaw", "openclaw.json");
3469
- if (!fs11.existsSync(configPath)) return;
3470
- const raw = fs11.readFileSync(configPath, "utf-8");
3471
- const config = JSON.parse(raw);
3472
- if (!config.skills?.entries) return;
3473
3612
  const approved = loadApprovedSkills();
3474
3613
  const approvedNames = new Set(approved.map((a) => a.name));
3475
- const entries = Object.keys(config.skills.entries);
3476
- let changed = false;
3477
- for (const name of entries) {
3614
+ const config = readOpenClawConfig();
3615
+ if (!config.skills?.entries) return;
3616
+ for (const name of Object.keys(config.skills.entries)) {
3478
3617
  if (!approvedNames.has(name)) {
3479
- delete config.skills.entries[name];
3480
- changed = true;
3481
- console.log(`[SkillsWatcher] Removed stale openclaw.json entry: ${name}`);
3618
+ removeSkillEntry(name);
3619
+ console.log(`[SkillsWatcher] Disabled stale openclaw.json entry: ${name}`);
3482
3620
  }
3483
3621
  }
3484
- if (changed) {
3485
- fs11.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
3486
- }
3487
3622
  } catch {
3488
3623
  }
3489
3624
  }
@@ -3550,7 +3685,7 @@ function approveSkill(skillName) {
3550
3685
  const cachedFiles = getDownloadedSkillFiles(slug);
3551
3686
  let hash;
3552
3687
  if (cachedFiles.length > 0) {
3553
- const h = crypto3.createHash("sha256");
3688
+ const h = crypto4.createHash("sha256");
3554
3689
  const sorted = [...cachedFiles].sort((a, b) => a.name.localeCompare(b.name));
3555
3690
  for (const file of sorted) {
3556
3691
  h.update(file.name);
@@ -3622,14 +3757,15 @@ function triggerSkillsScan() {
3622
3757
  function getSkillsDir() {
3623
3758
  return skillsDir;
3624
3759
  }
3625
- function addToApprovedList(skillName, publisher, hash) {
3760
+ function addToApprovedList(skillName, publisher, hash, slug) {
3626
3761
  const approved = loadApprovedSkills();
3627
3762
  if (!approved.some((s) => s.name === skillName)) {
3628
3763
  approved.push({
3629
3764
  name: skillName,
3630
3765
  approvedAt: (/* @__PURE__ */ new Date()).toISOString(),
3631
3766
  ...publisher ? { publisher } : {},
3632
- ...hash ? { hash } : {}
3767
+ ...hash ? { hash } : {},
3768
+ ...slug ? { slug } : {}
3633
3769
  });
3634
3770
  saveApprovedSkills(approved);
3635
3771
  }
@@ -3876,7 +4012,7 @@ async function securityRoutes(app) {
3876
4012
  // libs/shield-daemon/src/auth/passcode.ts
3877
4013
  init_vault();
3878
4014
  init_state();
3879
- import * as crypto4 from "node:crypto";
4015
+ import * as crypto5 from "node:crypto";
3880
4016
  import { DEFAULT_AUTH_CONFIG as DEFAULT_AUTH_CONFIG2 } from "@agenshield/ipc";
3881
4017
  var ITERATIONS = 1e5;
3882
4018
  var KEY_LENGTH = 64;
@@ -3884,8 +4020,8 @@ var DIGEST = "sha512";
3884
4020
  var SALT_LENGTH = 16;
3885
4021
  function hashPasscode(passcode) {
3886
4022
  return new Promise((resolve3, reject) => {
3887
- const salt = crypto4.randomBytes(SALT_LENGTH);
3888
- crypto4.pbkdf2(passcode, salt, ITERATIONS, KEY_LENGTH, DIGEST, (err, derivedKey) => {
4023
+ const salt = crypto5.randomBytes(SALT_LENGTH);
4024
+ crypto5.pbkdf2(passcode, salt, ITERATIONS, KEY_LENGTH, DIGEST, (err, derivedKey) => {
3889
4025
  if (err) {
3890
4026
  reject(err);
3891
4027
  return;
@@ -3905,12 +4041,12 @@ function verifyPasscode(passcode, storedHash) {
3905
4041
  const iterations = parseInt(parts[0], 10);
3906
4042
  const salt = Buffer.from(parts[1], "base64");
3907
4043
  const hash = Buffer.from(parts[2], "base64");
3908
- crypto4.pbkdf2(passcode, salt, iterations, hash.length, DIGEST, (err, derivedKey) => {
4044
+ crypto5.pbkdf2(passcode, salt, iterations, hash.length, DIGEST, (err, derivedKey) => {
3909
4045
  if (err) {
3910
4046
  reject(err);
3911
4047
  return;
3912
4048
  }
3913
- resolve3(crypto4.timingSafeEqual(hash, derivedKey));
4049
+ resolve3(crypto5.timingSafeEqual(hash, derivedKey));
3914
4050
  });
3915
4051
  });
3916
4052
  }
@@ -4042,16 +4178,16 @@ var PROTECTED_ROUTES = [
4042
4178
  { method: "POST", path: "/api/skills/install" },
4043
4179
  { method: "GET", path: "/api/openclaw/dashboard-url" }
4044
4180
  ];
4045
- function isProtectedRoute(method, path21) {
4181
+ function isProtectedRoute(method, path24) {
4046
4182
  return PROTECTED_ROUTES.some(
4047
- (route) => route.method === method && path21.startsWith(route.path)
4183
+ (route) => route.method === method && path24.startsWith(route.path)
4048
4184
  );
4049
4185
  }
4050
4186
  function createAuthHook() {
4051
4187
  return async (request2, reply) => {
4052
4188
  const method = request2.method;
4053
- const path21 = request2.url.split("?")[0];
4054
- if (!isProtectedRoute(method, path21)) {
4189
+ const path24 = request2.url.split("?")[0];
4190
+ if (!isProtectedRoute(method, path24)) {
4055
4191
  return;
4056
4192
  }
4057
4193
  if (!isAuthenticated(request2)) {
@@ -4082,6 +4218,12 @@ data: ${data}
4082
4218
 
4083
4219
  `;
4084
4220
  }
4221
+ var ALWAYS_FULL_PREFIXES = ["skills:", "exec:", "interceptor:", "security:", "wrappers:", "process:", "config:"];
4222
+ function shouldSendFull(event, authenticated) {
4223
+ if (authenticated) return true;
4224
+ if (event.type === "heartbeat" || event.type === "daemon:status") return true;
4225
+ return ALWAYS_FULL_PREFIXES.some((p) => event.type.startsWith(p));
4226
+ }
4085
4227
  async function sseRoutes(app) {
4086
4228
  app.get("/sse/events", async (request2, reply) => {
4087
4229
  const authenticated = isAuthenticated(request2);
@@ -4107,7 +4249,7 @@ async function sseRoutes(app) {
4107
4249
  reply.raw.write(authenticated ? formatSSE(statusEvent) : formatStrippedSSE(statusEvent));
4108
4250
  const unsubscribe = daemonEvents.subscribe((event) => {
4109
4251
  try {
4110
- if (event.type === "heartbeat" || event.type === "daemon:status" || event.type.startsWith("skills:") || authenticated) {
4252
+ if (shouldSendFull(event, authenticated)) {
4111
4253
  reply.raw.write(formatSSE(event));
4112
4254
  } else {
4113
4255
  reply.raw.write(formatStrippedSSE(event));
@@ -4155,7 +4297,7 @@ async function sseRoutes(app) {
4155
4297
  const unsubscribe = daemonEvents.subscribe((event) => {
4156
4298
  if (event.type.startsWith(filter) || event.type === "heartbeat" || event.type === "daemon:status") {
4157
4299
  try {
4158
- if (event.type === "heartbeat" || event.type === "daemon:status" || authenticated) {
4300
+ if (shouldSendFull(event, authenticated)) {
4159
4301
  reply.raw.write(formatSSE(event));
4160
4302
  } else {
4161
4303
  reply.raw.write(formatStrippedSSE(event));
@@ -7550,19 +7692,50 @@ async function agencoRoutes(app) {
7550
7692
  }
7551
7693
 
7552
7694
  // libs/shield-daemon/src/routes/skills.ts
7553
- import * as fs16 from "node:fs";
7554
- import * as path16 from "node:path";
7695
+ import * as fs17 from "node:fs";
7696
+ import * as path17 from "node:path";
7555
7697
  import { execSync as execSync9 } from "node:child_process";
7556
- import { parseSkillMd as parseSkillMd2 } from "@agenshield/sandbox";
7698
+ import { parseSkillMd as parseSkillMd3, stripEnvFromSkillMd as stripEnvFromSkillMd2 } from "@agenshield/sandbox";
7557
7699
 
7558
7700
  // libs/shield-daemon/src/services/skill-lifecycle.ts
7559
7701
  import * as fs14 from "node:fs";
7560
7702
  import * as path14 from "node:path";
7561
7703
  import { execSync as execSync8 } from "node:child_process";
7562
- function createSkillWrapper(name, binDir) {
7563
- if (!fs14.existsSync(binDir)) {
7564
- fs14.mkdirSync(binDir, { recursive: true });
7704
+ function sudoMkdir(dir, agentUsername) {
7705
+ try {
7706
+ fs14.mkdirSync(dir, { recursive: true });
7707
+ } catch (err) {
7708
+ if (err.code === "EACCES") {
7709
+ execSync8(`sudo -H -u ${agentUsername} /bin/mkdir -p "${dir}"`, { cwd: "/", stdio: "pipe" });
7710
+ } else {
7711
+ throw err;
7712
+ }
7713
+ }
7714
+ }
7715
+ function sudoWriteFile(filePath, content, agentUsername, mode) {
7716
+ try {
7717
+ fs14.writeFileSync(filePath, content, { mode });
7718
+ } catch (err) {
7719
+ if (err.code === "EACCES") {
7720
+ execSync8(
7721
+ `sudo -H -u ${agentUsername} tee "${filePath}" > /dev/null`,
7722
+ { input: content, cwd: "/", stdio: ["pipe", "pipe", "pipe"] }
7723
+ );
7724
+ if (mode) {
7725
+ try {
7726
+ execSync8(`sudo -H -u ${agentUsername} chmod ${mode.toString(8)} "${filePath}"`, { cwd: "/", stdio: "pipe" });
7727
+ } catch {
7728
+ }
7729
+ }
7730
+ } else {
7731
+ throw err;
7732
+ }
7565
7733
  }
7734
+ }
7735
+ function createSkillWrapper(name, binDir) {
7736
+ const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
7737
+ const agentUsername = path14.basename(agentHome);
7738
+ sudoMkdir(binDir, agentUsername);
7566
7739
  const wrapperPath = path14.join(binDir, name);
7567
7740
  const wrapperContent = `#!/bin/bash
7568
7741
  # ${name} skill wrapper - policy-enforced execution
@@ -7570,7 +7743,7 @@ function createSkillWrapper(name, binDir) {
7570
7743
  if ! /bin/pwd > /dev/null 2>&1; then cd ~ 2>/dev/null || cd /; fi
7571
7744
  exec /opt/agenshield/bin/shield-client skill run "${name}" "$@"
7572
7745
  `;
7573
- fs14.writeFileSync(wrapperPath, wrapperContent, { mode: 493 });
7746
+ sudoWriteFile(wrapperPath, wrapperContent, agentUsername, 493);
7574
7747
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
7575
7748
  try {
7576
7749
  execSync8(`chown root:${socketGroup} "${wrapperPath}"`, { stdio: "pipe" });
@@ -7615,8 +7788,8 @@ function removeSkillPolicy(name) {
7615
7788
  }
7616
7789
 
7617
7790
  // libs/shield-daemon/src/routes/marketplace.ts
7618
- import * as fs15 from "node:fs";
7619
- import * as path15 from "node:path";
7791
+ import * as fs16 from "node:fs";
7792
+ import * as path16 from "node:path";
7620
7793
 
7621
7794
  // libs/shield-broker/dist/index.js
7622
7795
  import { exec as exec4 } from "node:child_process";
@@ -7892,6 +8065,176 @@ async function uninstallSkillViaBroker(slug, options = {}) {
7892
8065
  return result;
7893
8066
  }
7894
8067
 
8068
+ // libs/shield-daemon/src/routes/marketplace.ts
8069
+ import { stripEnvFromSkillMd } from "@agenshield/sandbox";
8070
+
8071
+ // libs/shield-daemon/src/services/skill-deps.ts
8072
+ import * as fs15 from "node:fs";
8073
+ import * as path15 from "node:path";
8074
+ import { parseSkillMd as parseSkillMd2, extractSkillInfo } from "@agenshield/sandbox";
8075
+ import { execWithProgress as execWithProgress3 } from "@agenshield/sandbox";
8076
+ var SUPPORTED_KINDS = /* @__PURE__ */ new Set(["brew", "npm", "pip"]);
8077
+ var SAFE_PACKAGE_RE = /^[a-zA-Z0-9@/_.\-]+$/;
8078
+ var SYSTEM_PATH = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
8079
+ function findSkillMdRecursive(dir, depth = 0) {
8080
+ if (depth > 3) return null;
8081
+ try {
8082
+ for (const name of ["SKILL.md", "skill.md", "README.md", "readme.md"]) {
8083
+ const candidate = path15.join(dir, name);
8084
+ if (fs15.existsSync(candidate)) return candidate;
8085
+ }
8086
+ const entries = fs15.readdirSync(dir, { withFileTypes: true });
8087
+ for (const entry of entries) {
8088
+ if (!entry.isDirectory()) continue;
8089
+ const found = findSkillMdRecursive(path15.join(dir, entry.name), depth + 1);
8090
+ if (found) return found;
8091
+ }
8092
+ } catch {
8093
+ }
8094
+ return null;
8095
+ }
8096
+ async function executeSkillInstallSteps(options) {
8097
+ const { slug, skillDir, agentHome, agentUsername, onLog } = options;
8098
+ const installed = [];
8099
+ const errors = [];
8100
+ const skillMdPath = findSkillMdRecursive(skillDir);
8101
+ if (!skillMdPath) {
8102
+ return { success: true, installed, errors };
8103
+ }
8104
+ let content;
8105
+ try {
8106
+ content = fs15.readFileSync(skillMdPath, "utf-8");
8107
+ } catch {
8108
+ return { success: true, installed, errors };
8109
+ }
8110
+ const parsed = parseSkillMd2(content);
8111
+ if (!parsed) {
8112
+ return { success: true, installed, errors };
8113
+ }
8114
+ const info = extractSkillInfo(parsed.metadata);
8115
+ const installSteps = info.installSteps;
8116
+ if (!Array.isArray(installSteps) || installSteps.length === 0) {
8117
+ return { success: true, installed, errors };
8118
+ }
8119
+ onLog(`Found ${installSteps.length} dependency install step(s) for ${slug}`);
8120
+ for (const step of installSteps) {
8121
+ const kind = step.kind;
8122
+ const stepId = step.id || kind;
8123
+ if (!SUPPORTED_KINDS.has(kind)) {
8124
+ errors.push(`Unsupported install kind "${kind}" (step: ${stepId})`);
8125
+ continue;
8126
+ }
8127
+ try {
8128
+ switch (kind) {
8129
+ case "brew": {
8130
+ const formula = step.formula;
8131
+ if (!formula) {
8132
+ errors.push(`Brew step "${stepId}" missing formula`);
8133
+ break;
8134
+ }
8135
+ if (!SAFE_PACKAGE_RE.test(formula)) {
8136
+ errors.push(`Unsafe brew formula name: ${formula}`);
8137
+ break;
8138
+ }
8139
+ onLog(`Installing brew formula: ${formula}`);
8140
+ const brewCmd = [
8141
+ `export HOME="${agentHome}"`,
8142
+ `export PATH="${agentHome}/homebrew/bin:${agentHome}/bin:${SYSTEM_PATH}:$PATH"`,
8143
+ `brew install ${formula}`
8144
+ ].join(" && ");
8145
+ await execWithProgress3(
8146
+ `sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${brewCmd}'`,
8147
+ onLog,
8148
+ { timeout: 12e4, cwd: "/" }
8149
+ );
8150
+ installed.push(formula);
8151
+ break;
8152
+ }
8153
+ case "npm": {
8154
+ const pkg = step["package"];
8155
+ if (!pkg) {
8156
+ errors.push(`npm step "${stepId}" missing package`);
8157
+ break;
8158
+ }
8159
+ if (!SAFE_PACKAGE_RE.test(pkg)) {
8160
+ errors.push(`Unsafe npm package name: ${pkg}`);
8161
+ break;
8162
+ }
8163
+ onLog(`Installing npm package: ${pkg}`);
8164
+ const npmCmd = [
8165
+ `export HOME="${agentHome}"`,
8166
+ `export PATH="${agentHome}/bin:${SYSTEM_PATH}:$PATH"`,
8167
+ `source "${agentHome}/.nvm/nvm.sh" 2>/dev/null || true`,
8168
+ `npm install -g ${pkg}`
8169
+ ].join(" && ");
8170
+ await execWithProgress3(
8171
+ `sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${npmCmd}'`,
8172
+ onLog,
8173
+ { timeout: 6e4, cwd: "/" }
8174
+ );
8175
+ installed.push(pkg);
8176
+ break;
8177
+ }
8178
+ case "pip": {
8179
+ const pkg = step["package"];
8180
+ if (!pkg) {
8181
+ errors.push(`pip step "${stepId}" missing package`);
8182
+ break;
8183
+ }
8184
+ if (!SAFE_PACKAGE_RE.test(pkg)) {
8185
+ errors.push(`Unsafe pip package name: ${pkg}`);
8186
+ break;
8187
+ }
8188
+ onLog(`Installing pip package: ${pkg}`);
8189
+ const pipCmd = [
8190
+ `export HOME="${agentHome}"`,
8191
+ `export PATH="${agentHome}/bin:${SYSTEM_PATH}:$PATH"`,
8192
+ `pip install ${pkg}`
8193
+ ].join(" && ");
8194
+ await execWithProgress3(
8195
+ `sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${pipCmd}'`,
8196
+ onLog,
8197
+ { timeout: 6e4, cwd: "/" }
8198
+ );
8199
+ installed.push(pkg);
8200
+ break;
8201
+ }
8202
+ }
8203
+ } catch (err) {
8204
+ const msg = `Failed to install ${kind} dep (step: ${stepId}): ${err.message}`;
8205
+ onLog(msg);
8206
+ errors.push(msg);
8207
+ }
8208
+ }
8209
+ const requiredBins = info.bins;
8210
+ if (requiredBins.length > 0) {
8211
+ onLog(`Verifying required binaries: ${requiredBins.join(", ")}`);
8212
+ for (const bin of requiredBins) {
8213
+ try {
8214
+ const checkCmd = [
8215
+ `export HOME="${agentHome}"`,
8216
+ `export PATH="${agentHome}/homebrew/bin:${agentHome}/bin:${SYSTEM_PATH}:$PATH"`,
8217
+ `source "${agentHome}/.nvm/nvm.sh" 2>/dev/null || true`,
8218
+ `which ${bin}`
8219
+ ].join(" && ");
8220
+ await execWithProgress3(
8221
+ `sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${checkCmd}'`,
8222
+ () => {
8223
+ },
8224
+ { timeout: 5e3, cwd: "/" }
8225
+ );
8226
+ } catch {
8227
+ errors.push(`Required binary "${bin}" not found in agent PATH after install`);
8228
+ }
8229
+ }
8230
+ }
8231
+ return {
8232
+ success: errors.length === 0,
8233
+ installed,
8234
+ errors
8235
+ };
8236
+ }
8237
+
7895
8238
  // libs/shield-daemon/src/routes/marketplace.ts
7896
8239
  var installInProgress = /* @__PURE__ */ new Set();
7897
8240
  function isInstallInProgress(slug) {
@@ -8146,7 +8489,7 @@ async function marketplaceRoutes(app) {
8146
8489
  error: "Critical vulnerability detected",
8147
8490
  analysis: analysisResult
8148
8491
  });
8149
- return { success: false, name: slug, analysis: analysisResult, logs };
8492
+ return { success: false, name: slug, analysis: analysisResult, logs, depsSuccess: void 0 };
8150
8493
  }
8151
8494
  emitSkillInstallProgress(slug, "download", "Downloading skill files");
8152
8495
  const skill = await getMarketplaceSkill(slug);
@@ -8156,7 +8499,7 @@ async function marketplaceRoutes(app) {
8156
8499
  name: slug,
8157
8500
  error: "No files available for installation"
8158
8501
  });
8159
- return { success: false, name: slug, analysis: analysisResult, logs };
8502
+ return { success: false, name: slug, analysis: analysisResult, logs, depsSuccess: void 0 };
8160
8503
  }
8161
8504
  const publisher = skill.author;
8162
8505
  logs.push("Downloaded skill files");
@@ -8165,18 +8508,27 @@ async function marketplaceRoutes(app) {
8165
8508
  throw new Error("Skills directory not configured");
8166
8509
  }
8167
8510
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
8168
- const binDir = path15.join(agentHome, "bin");
8511
+ const agentUsername = path16.basename(agentHome);
8512
+ const binDir = path16.join(agentHome, "bin");
8169
8513
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
8170
- skillDir = path15.join(skillsDir2, slug);
8514
+ skillDir = path16.join(skillsDir2, slug);
8171
8515
  emitSkillInstallProgress(slug, "approve", "Pre-approving skill");
8172
- addToApprovedList(slug, publisher);
8516
+ addToApprovedList(slug, publisher, void 0, slug);
8173
8517
  logs.push("Skill pre-approved");
8174
8518
  emitSkillInstallProgress(slug, "copy", "Writing skill files");
8519
+ const taggedFiles = await Promise.all(files.map(async (f) => {
8520
+ let content = f.content;
8521
+ if (/SKILL\.md$/i.test(f.name)) {
8522
+ content = stripEnvFromSkillMd(content);
8523
+ content = await injectInstallationTag(content);
8524
+ }
8525
+ return { ...f, content };
8526
+ }));
8175
8527
  const brokerAvailable = await isBrokerAvailable();
8176
8528
  if (brokerAvailable) {
8177
8529
  const brokerResult = await installSkillViaBroker(
8178
8530
  slug,
8179
- files.map((f) => ({ name: f.name, content: f.content })),
8531
+ taggedFiles.map((f) => ({ name: f.name, content: f.content })),
8180
8532
  { createWrapper: true, agentHome, socketGroup }
8181
8533
  );
8182
8534
  if (!brokerResult.installed) {
@@ -8195,18 +8547,65 @@ async function marketplaceRoutes(app) {
8195
8547
  }
8196
8548
  } else {
8197
8549
  console.log(`[Marketplace] Broker unavailable, installing ${slug} directly`);
8198
- fs15.mkdirSync(skillDir, { recursive: true });
8199
- for (const file of files) {
8200
- const filePath = path15.join(skillDir, file.name);
8201
- const fileDir = path15.dirname(filePath);
8550
+ sudoMkdir(skillDir, agentUsername);
8551
+ for (const file of taggedFiles) {
8552
+ const filePath = path16.join(skillDir, file.name);
8553
+ const fileDir = path16.dirname(filePath);
8202
8554
  if (fileDir !== skillDir) {
8203
- fs15.mkdirSync(fileDir, { recursive: true });
8555
+ sudoMkdir(fileDir, agentUsername);
8204
8556
  }
8205
- fs15.writeFileSync(filePath, file.content, "utf-8");
8557
+ sudoWriteFile(filePath, file.content, agentUsername);
8206
8558
  }
8207
8559
  createSkillWrapper(slug, binDir);
8208
- logs.push(`Files written directly: ${files.length} files`);
8209
- logs.push(`Wrapper created: ${path15.join(binDir, slug)}`);
8560
+ logs.push(`Files written directly: ${taggedFiles.length} files`);
8561
+ logs.push(`Wrapper created: ${path16.join(binDir, slug)}`);
8562
+ }
8563
+ let depsSuccess = true;
8564
+ emitSkillInstallProgress(slug, "deps", "Installing skill dependencies");
8565
+ try {
8566
+ let depsLineCount = 0;
8567
+ let depsLastEmit = Date.now();
8568
+ let depsLastLine = "";
8569
+ const DEPS_DEBOUNCE_MS = 3e3;
8570
+ const depsOnLog = (msg) => {
8571
+ depsLineCount++;
8572
+ depsLastLine = msg;
8573
+ if (/^(Installing|Found|Verifying)\s/.test(msg)) {
8574
+ emitSkillInstallProgress(slug, "deps", msg);
8575
+ depsLastEmit = Date.now();
8576
+ return;
8577
+ }
8578
+ const now = Date.now();
8579
+ if (now - depsLastEmit >= DEPS_DEBOUNCE_MS) {
8580
+ emitSkillInstallProgress(slug, "deps", `Installing... (${depsLineCount} lines)`);
8581
+ depsLastEmit = now;
8582
+ }
8583
+ };
8584
+ const depsResult = await executeSkillInstallSteps({
8585
+ slug,
8586
+ skillDir,
8587
+ agentHome,
8588
+ agentUsername,
8589
+ onLog: depsOnLog
8590
+ });
8591
+ if (depsLineCount > 0) {
8592
+ emitSkillInstallProgress(slug, "deps", `Dependency install complete (${depsLineCount} lines processed)`);
8593
+ }
8594
+ if (depsResult.installed.length > 0) {
8595
+ logs.push(`Dependencies installed: ${depsResult.installed.join(", ")}`);
8596
+ }
8597
+ if (depsResult.errors.length > 0) {
8598
+ depsSuccess = false;
8599
+ for (const err of depsResult.errors) {
8600
+ emitSkillInstallProgress(slug, "warning", `Dependency warning: ${err}`);
8601
+ logs.push(`Dependency warning: ${err}`);
8602
+ }
8603
+ }
8604
+ } catch (err) {
8605
+ depsSuccess = false;
8606
+ const msg = `Dependency installation failed: ${err.message}`;
8607
+ emitSkillInstallProgress(slug, "warning", msg);
8608
+ logs.push(msg);
8210
8609
  }
8211
8610
  addSkillEntry(slug);
8212
8611
  addSkillPolicy(slug);
@@ -8217,14 +8616,19 @@ async function marketplaceRoutes(app) {
8217
8616
  updateApprovedHash(slug, hash);
8218
8617
  logs.push("Integrity hash recorded");
8219
8618
  }
8619
+ try {
8620
+ markDownloadedAsInstalled(slug);
8621
+ } catch {
8622
+ }
8220
8623
  installInProgress.delete(slug);
8221
- daemonEvents.broadcast("skills:installed", { name: slug, analysis: analysisResult });
8624
+ const depsWarnings = depsSuccess ? void 0 : logs.filter((l) => l.startsWith("Dependency"));
8625
+ daemonEvents.broadcast("skills:installed", { name: slug, analysis: analysisResult, depsWarnings });
8222
8626
  logs.push("Installation complete");
8223
- return { success: true, name: slug, analysis: analysisResult, logs };
8627
+ return { success: true, name: slug, analysis: analysisResult, logs, depsSuccess };
8224
8628
  } catch (err) {
8225
8629
  try {
8226
- if (skillDir && fs15.existsSync(skillDir)) {
8227
- fs15.rmSync(skillDir, { recursive: true, force: true });
8630
+ if (skillDir && fs16.existsSync(skillDir)) {
8631
+ fs16.rmSync(skillDir, { recursive: true, force: true });
8228
8632
  }
8229
8633
  removeFromApprovedList(slug);
8230
8634
  } catch {
@@ -8233,7 +8637,7 @@ async function marketplaceRoutes(app) {
8233
8637
  installInProgress.delete(slug);
8234
8638
  daemonEvents.broadcast("skills:install_failed", { name: slug, error: errorMsg });
8235
8639
  console.error("[Marketplace] Install failed:", errorMsg);
8236
- return { success: false, name: slug, analysis: analysisResult, logs: [...logs, `Error: ${errorMsg}`] };
8640
+ return { success: false, name: slug, analysis: analysisResult, logs: [...logs, `Error: ${errorMsg}`], depsSuccess: void 0 };
8237
8641
  } finally {
8238
8642
  installInProgress.delete(slug);
8239
8643
  }
@@ -8257,17 +8661,17 @@ async function marketplaceRoutes(app) {
8257
8661
  }
8258
8662
 
8259
8663
  // libs/shield-daemon/src/routes/skills.ts
8260
- function findSkillMdRecursive(dir, depth = 0) {
8664
+ function findSkillMdRecursive2(dir, depth = 0) {
8261
8665
  if (depth > 3) return null;
8262
8666
  try {
8263
8667
  for (const name of ["SKILL.md", "skill.md", "README.md", "readme.md"]) {
8264
- const candidate = path16.join(dir, name);
8265
- if (fs16.existsSync(candidate)) return candidate;
8668
+ const candidate = path17.join(dir, name);
8669
+ if (fs17.existsSync(candidate)) return candidate;
8266
8670
  }
8267
- const entries = fs16.readdirSync(dir, { withFileTypes: true });
8671
+ const entries = fs17.readdirSync(dir, { withFileTypes: true });
8268
8672
  for (const entry of entries) {
8269
8673
  if (!entry.isDirectory()) continue;
8270
- const found = findSkillMdRecursive(path16.join(dir, entry.name), depth + 1);
8674
+ const found = findSkillMdRecursive2(path17.join(dir, entry.name), depth + 1);
8271
8675
  if (found) return found;
8272
8676
  }
8273
8677
  } catch {
@@ -8276,10 +8680,10 @@ function findSkillMdRecursive(dir, depth = 0) {
8276
8680
  }
8277
8681
  function readSkillMetadata(skillDir) {
8278
8682
  try {
8279
- const mdPath = findSkillMdRecursive(skillDir);
8683
+ const mdPath = findSkillMdRecursive2(skillDir);
8280
8684
  if (!mdPath) return {};
8281
- const content = fs16.readFileSync(mdPath, "utf-8");
8282
- const parsed = parseSkillMd2(content);
8685
+ const content = fs17.readFileSync(mdPath, "utf-8");
8686
+ const parsed = parseSkillMd3(content);
8283
8687
  const meta = parsed?.metadata;
8284
8688
  return {
8285
8689
  description: meta?.description,
@@ -8337,7 +8741,7 @@ async function skillsRoutes(app) {
8337
8741
  let onDiskNames = [];
8338
8742
  if (skillsDir2) {
8339
8743
  try {
8340
- onDiskNames = fs16.readdirSync(skillsDir2, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
8744
+ onDiskNames = fs17.readdirSync(skillsDir2, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
8341
8745
  } catch {
8342
8746
  }
8343
8747
  }
@@ -8347,14 +8751,14 @@ async function skillsRoutes(app) {
8347
8751
  const data = [
8348
8752
  // Approved → active (with metadata from SKILL.md + cached analysis)
8349
8753
  ...approved.map((a) => {
8350
- const meta = skillsDir2 ? readSkillMetadata(path16.join(skillsDir2, a.name)) : {};
8754
+ const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, a.name)) : {};
8351
8755
  const cached = getCachedAnalysis2(a.name);
8352
8756
  const dlMeta = getDownloadedSkillMeta(a.name);
8353
8757
  return {
8354
8758
  name: a.name,
8355
8759
  source: "user",
8356
8760
  status: "active",
8357
- path: path16.join(skillsDir2 ?? "", a.name),
8761
+ path: path17.join(skillsDir2 ?? "", a.name),
8358
8762
  publisher: a.publisher,
8359
8763
  description: meta.description,
8360
8764
  version: meta.version,
@@ -8381,19 +8785,42 @@ async function skillsRoutes(app) {
8381
8785
  }),
8382
8786
  // Workspace: on disk but not approved or untrusted
8383
8787
  ...workspaceNames.map((name) => {
8384
- const meta = skillsDir2 ? readSkillMetadata(path16.join(skillsDir2, name)) : {};
8788
+ const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, name)) : {};
8385
8789
  return {
8386
8790
  name,
8387
8791
  source: "workspace",
8388
8792
  status: "workspace",
8389
- path: path16.join(skillsDir2 ?? "", name),
8793
+ path: path17.join(skillsDir2 ?? "", name),
8390
8794
  description: meta.description,
8391
8795
  version: meta.version,
8392
8796
  author: meta.author,
8393
8797
  tags: meta.tags,
8394
8798
  analysis: buildAnalysisSummary(name, getCachedAnalysis2(name))
8395
8799
  };
8396
- })
8800
+ }),
8801
+ // Disabled: previously installed marketplace skills that are no longer active
8802
+ ...(() => {
8803
+ const allKnown = /* @__PURE__ */ new Set([
8804
+ ...approvedNames,
8805
+ ...untrustedNames,
8806
+ ...workspaceNames
8807
+ ]);
8808
+ return listDownloadedSkills().filter((d) => d.wasInstalled && !allKnown.has(d.slug) && !allKnown.has(d.name)).map((d) => {
8809
+ const cached = getCachedAnalysis2(d.slug) || getCachedAnalysis2(d.name);
8810
+ return {
8811
+ name: d.slug,
8812
+ source: "marketplace",
8813
+ status: "disabled",
8814
+ path: "",
8815
+ publisher: d.author,
8816
+ description: d.description,
8817
+ version: d.version,
8818
+ author: d.author,
8819
+ tags: d.tags,
8820
+ analysis: buildAnalysisSummary(d.slug, d.analysis || cached)
8821
+ };
8822
+ });
8823
+ })()
8397
8824
  ];
8398
8825
  return reply.send({ data });
8399
8826
  });
@@ -8417,7 +8844,7 @@ async function skillsRoutes(app) {
8417
8844
  let isWorkspace = false;
8418
8845
  if (!entry && !uEntry && skillsDir2) {
8419
8846
  try {
8420
- isWorkspace = fs16.existsSync(path16.join(skillsDir2, name));
8847
+ isWorkspace = fs17.existsSync(path17.join(skillsDir2, name));
8421
8848
  } catch {
8422
8849
  }
8423
8850
  }
@@ -8435,13 +8862,13 @@ async function skillsRoutes(app) {
8435
8862
  analysis: buildFullAnalysis(name, dlMeta?.analysis || getCachedAnalysis2(name))
8436
8863
  };
8437
8864
  } else if (entry) {
8438
- const meta = skillsDir2 ? readSkillMetadata(path16.join(skillsDir2, name)) : {};
8865
+ const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, name)) : {};
8439
8866
  const cached = getCachedAnalysis2(name);
8440
8867
  summary = {
8441
8868
  name,
8442
8869
  source: "user",
8443
8870
  status: "active",
8444
- path: skillsDir2 ? path16.join(skillsDir2, name) : "",
8871
+ path: skillsDir2 ? path17.join(skillsDir2, name) : "",
8445
8872
  publisher: entry.publisher,
8446
8873
  description: meta.description,
8447
8874
  version: meta.version,
@@ -8450,12 +8877,12 @@ async function skillsRoutes(app) {
8450
8877
  analysis: buildFullAnalysis(name, dlMeta?.analysis || cached)
8451
8878
  };
8452
8879
  } else if (isWorkspace) {
8453
- const meta = skillsDir2 ? readSkillMetadata(path16.join(skillsDir2, name)) : {};
8880
+ const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, name)) : {};
8454
8881
  summary = {
8455
8882
  name,
8456
8883
  source: "workspace",
8457
8884
  status: "workspace",
8458
- path: skillsDir2 ? path16.join(skillsDir2, name) : "",
8885
+ path: skillsDir2 ? path17.join(skillsDir2, name) : "",
8459
8886
  description: meta.description,
8460
8887
  version: meta.version,
8461
8888
  author: meta.author,
@@ -8467,7 +8894,7 @@ async function skillsRoutes(app) {
8467
8894
  summary = {
8468
8895
  name: dlMeta.slug ?? name,
8469
8896
  source: "marketplace",
8470
- status: "downloaded",
8897
+ status: dlMeta.wasInstalled ? "disabled" : "downloaded",
8471
8898
  description: dlMeta.description,
8472
8899
  path: "",
8473
8900
  publisher: dlMeta.author,
@@ -8480,11 +8907,11 @@ async function skillsRoutes(app) {
8480
8907
  return reply.code(404).send({ error: `Skill "${name}" not found` });
8481
8908
  }
8482
8909
  let content = "";
8483
- const dirToRead = summary.path || (skillsDir2 ? path16.join(skillsDir2, name) : "");
8910
+ const dirToRead = summary.path || (skillsDir2 ? path17.join(skillsDir2, name) : "");
8484
8911
  if (dirToRead) {
8485
8912
  try {
8486
- const mdPath = findSkillMdRecursive(dirToRead);
8487
- if (mdPath) content = fs16.readFileSync(mdPath, "utf-8");
8913
+ const mdPath = findSkillMdRecursive2(dirToRead);
8914
+ if (mdPath) content = fs17.readFileSync(mdPath, "utf-8");
8488
8915
  } catch {
8489
8916
  }
8490
8917
  }
@@ -8522,14 +8949,14 @@ async function skillsRoutes(app) {
8522
8949
  if (!content) {
8523
8950
  const skillsDir2 = getSkillsDir();
8524
8951
  const possibleDirs = [
8525
- skillsDir2 ? path16.join(skillsDir2, name) : null
8952
+ skillsDir2 ? path17.join(skillsDir2, name) : null
8526
8953
  ].filter(Boolean);
8527
8954
  for (const dir of possibleDirs) {
8528
8955
  try {
8529
- const mdPath = findSkillMdRecursive(dir);
8956
+ const mdPath = findSkillMdRecursive2(dir);
8530
8957
  if (mdPath) {
8531
- content = fs16.readFileSync(mdPath, "utf-8");
8532
- const parsed = parseSkillMd2(content);
8958
+ content = fs17.readFileSync(mdPath, "utf-8");
8959
+ const parsed = parseSkillMd3(content);
8533
8960
  if (parsed?.metadata && !metadata) {
8534
8961
  metadata = parsed.metadata;
8535
8962
  }
@@ -8544,7 +8971,7 @@ async function skillsRoutes(app) {
8544
8971
  const skillFile = localFiles.find((f) => /skill\.md/i.test(f.name));
8545
8972
  if (skillFile?.content) {
8546
8973
  content = skillFile.content;
8547
- const parsed = parseSkillMd2(content);
8974
+ const parsed = parseSkillMd3(content);
8548
8975
  if (parsed?.metadata && !metadata) {
8549
8976
  metadata = parsed.metadata;
8550
8977
  }
@@ -8559,7 +8986,7 @@ async function skillsRoutes(app) {
8559
8986
  const skillFile = freshFiles.find((f) => /skill\.md/i.test(f.name));
8560
8987
  if (skillFile?.content) {
8561
8988
  content = skillFile.content;
8562
- const parsed = parseSkillMd2(content);
8989
+ const parsed = parseSkillMd3(content);
8563
8990
  if (parsed?.metadata && !metadata) {
8564
8991
  metadata = parsed.metadata;
8565
8992
  }
@@ -8713,10 +9140,10 @@ async function skillsRoutes(app) {
8713
9140
  return reply.code(500).send({ error: "Skills directory not configured" });
8714
9141
  }
8715
9142
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
8716
- const binDir = path16.join(agentHome, "bin");
9143
+ const binDir = path17.join(agentHome, "bin");
8717
9144
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
8718
- const skillDir = path16.join(skillsDir2, name);
8719
- const isInstalled = fs16.existsSync(skillDir);
9145
+ const skillDir = path17.join(skillsDir2, name);
9146
+ const isInstalled = fs17.existsSync(skillDir);
8720
9147
  if (isInstalled) {
8721
9148
  try {
8722
9149
  const brokerAvailable = await isBrokerAvailable();
@@ -8726,7 +9153,7 @@ async function skillsRoutes(app) {
8726
9153
  agentHome
8727
9154
  });
8728
9155
  } else {
8729
- fs16.rmSync(skillDir, { recursive: true, force: true });
9156
+ fs17.rmSync(skillDir, { recursive: true, force: true });
8730
9157
  removeSkillWrapper(name, binDir);
8731
9158
  }
8732
9159
  removeSkillEntry(name);
@@ -8734,7 +9161,7 @@ async function skillsRoutes(app) {
8734
9161
  syncOpenClawFromPolicies(loadConfig().policies);
8735
9162
  removeFromApprovedList(name);
8736
9163
  try {
8737
- deleteDownloadedSkill(name);
9164
+ markDownloadedAsInstalled(name);
8738
9165
  } catch {
8739
9166
  }
8740
9167
  console.log(`[Skills] Disabled marketplace skill: ${name}`);
@@ -8754,12 +9181,20 @@ async function skillsRoutes(app) {
8754
9181
  return reply.code(404).send({ error: "No files in download cache for this skill" });
8755
9182
  }
8756
9183
  try {
8757
- addToApprovedList(name, meta.author);
9184
+ addToApprovedList(name, meta.author, void 0, meta.slug);
9185
+ const taggedFiles = await Promise.all(files.map(async (f) => {
9186
+ let content = f.content;
9187
+ if (/SKILL\.md$/i.test(f.name)) {
9188
+ content = stripEnvFromSkillMd2(content);
9189
+ content = await injectInstallationTag(content);
9190
+ }
9191
+ return { name: f.name, content, type: f.type };
9192
+ }));
8758
9193
  const brokerAvailable = await isBrokerAvailable();
8759
9194
  if (brokerAvailable) {
8760
9195
  const brokerResult = await installSkillViaBroker(
8761
9196
  name,
8762
- files.map((f) => ({ name: f.name, content: f.content })),
9197
+ taggedFiles.map((f) => ({ name: f.name, content: f.content })),
8763
9198
  { createWrapper: true, agentHome, socketGroup }
8764
9199
  );
8765
9200
  if (!brokerResult.installed) {
@@ -8771,11 +9206,11 @@ async function skillsRoutes(app) {
8771
9206
  }
8772
9207
  }
8773
9208
  } else {
8774
- fs16.mkdirSync(skillDir, { recursive: true });
8775
- for (const file of files) {
8776
- const filePath = path16.join(skillDir, file.name);
8777
- fs16.mkdirSync(path16.dirname(filePath), { recursive: true });
8778
- fs16.writeFileSync(filePath, file.content, "utf-8");
9209
+ fs17.mkdirSync(skillDir, { recursive: true });
9210
+ for (const file of taggedFiles) {
9211
+ const filePath = path17.join(skillDir, file.name);
9212
+ fs17.mkdirSync(path17.dirname(filePath), { recursive: true });
9213
+ fs17.writeFileSync(filePath, file.content, "utf-8");
8779
9214
  }
8780
9215
  try {
8781
9216
  execSync9(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
@@ -8789,12 +9224,16 @@ async function skillsRoutes(app) {
8789
9224
  syncOpenClawFromPolicies(loadConfig().policies);
8790
9225
  const hash = computeSkillHash(skillDir);
8791
9226
  if (hash) updateApprovedHash(name, hash);
9227
+ try {
9228
+ markDownloadedAsInstalled(name);
9229
+ } catch {
9230
+ }
8792
9231
  console.log(`[Skills] Enabled marketplace skill: ${name}`);
8793
9232
  return reply.send({ success: true, action: "enabled", name });
8794
9233
  } catch (err) {
8795
9234
  try {
8796
- if (fs16.existsSync(skillDir)) {
8797
- fs16.rmSync(skillDir, { recursive: true, force: true });
9235
+ if (fs17.existsSync(skillDir)) {
9236
+ fs17.rmSync(skillDir, { recursive: true, force: true });
8798
9237
  }
8799
9238
  removeFromApprovedList(name);
8800
9239
  } catch {
@@ -8820,7 +9259,7 @@ async function skillsRoutes(app) {
8820
9259
  const skillMdFile = files.find((f) => f.name === "SKILL.md");
8821
9260
  let metadata;
8822
9261
  if (skillMdFile) {
8823
- const parsed = parseSkillMd2(skillMdFile.content);
9262
+ const parsed = parseSkillMd3(skillMdFile.content);
8824
9263
  metadata = parsed?.metadata;
8825
9264
  }
8826
9265
  const analysis = analyzeSkill(name, combinedContent, metadata);
@@ -8832,16 +9271,25 @@ async function skillsRoutes(app) {
8832
9271
  return reply.code(500).send({ error: "Skills directory not configured" });
8833
9272
  }
8834
9273
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
8835
- const binDir = path16.join(agentHome, "bin");
9274
+ const agentUsername = path17.basename(agentHome);
9275
+ const binDir = path17.join(agentHome, "bin");
8836
9276
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
8837
- const skillDir = path16.join(skillsDir2, name);
9277
+ const skillDir = path17.join(skillsDir2, name);
8838
9278
  try {
8839
9279
  addToApprovedList(name, publisher);
8840
- fs16.mkdirSync(skillDir, { recursive: true });
8841
- for (const file of files) {
8842
- const filePath = path16.join(skillDir, file.name);
8843
- fs16.mkdirSync(path16.dirname(filePath), { recursive: true });
8844
- fs16.writeFileSync(filePath, file.content, "utf-8");
9280
+ const taggedFiles = await Promise.all(files.map(async (f) => {
9281
+ let content = f.content;
9282
+ if (/SKILL\.md$/i.test(f.name)) {
9283
+ content = stripEnvFromSkillMd2(content);
9284
+ content = await injectInstallationTag(content);
9285
+ }
9286
+ return { name: f.name, content };
9287
+ }));
9288
+ sudoMkdir(skillDir, agentUsername);
9289
+ for (const file of taggedFiles) {
9290
+ const filePath = path17.join(skillDir, file.name);
9291
+ sudoMkdir(path17.dirname(filePath), agentUsername);
9292
+ sudoWriteFile(filePath, file.content, agentUsername);
8845
9293
  }
8846
9294
  try {
8847
9295
  execSync9(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
@@ -8855,8 +9303,8 @@ async function skillsRoutes(app) {
8855
9303
  return reply.send({ success: true, name, analysis });
8856
9304
  } catch (err) {
8857
9305
  try {
8858
- if (fs16.existsSync(skillDir)) {
8859
- fs16.rmSync(skillDir, { recursive: true, force: true });
9306
+ if (fs17.existsSync(skillDir)) {
9307
+ fs17.rmSync(skillDir, { recursive: true, force: true });
8860
9308
  }
8861
9309
  removeFromApprovedList(name);
8862
9310
  } catch {
@@ -8890,15 +9338,23 @@ async function skillsRoutes(app) {
8890
9338
  }
8891
9339
  try {
8892
9340
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
8893
- const binDir = path16.join(agentHome, "bin");
9341
+ const binDir = path17.join(agentHome, "bin");
8894
9342
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
8895
- const skillDir = path16.join(getSkillsDir(), name);
8896
- addToApprovedList(name, meta.author);
9343
+ const skillDir = path17.join(getSkillsDir(), name);
9344
+ addToApprovedList(name, meta.author, void 0, meta.slug);
9345
+ const taggedFiles = await Promise.all(files.map(async (f) => {
9346
+ let content = f.content;
9347
+ if (/SKILL\.md$/i.test(f.name)) {
9348
+ content = stripEnvFromSkillMd2(content);
9349
+ content = await injectInstallationTag(content);
9350
+ }
9351
+ return { name: f.name, content, type: f.type };
9352
+ }));
8897
9353
  const brokerAvailable = await isBrokerAvailable();
8898
9354
  if (brokerAvailable) {
8899
9355
  const brokerResult = await installSkillViaBroker(
8900
9356
  name,
8901
- files.map((f) => ({ name: f.name, content: f.content })),
9357
+ taggedFiles.map((f) => ({ name: f.name, content: f.content })),
8902
9358
  { createWrapper: true, agentHome, socketGroup }
8903
9359
  );
8904
9360
  if (!brokerResult.installed) {
@@ -8910,11 +9366,11 @@ async function skillsRoutes(app) {
8910
9366
  }
8911
9367
  }
8912
9368
  } else {
8913
- fs16.mkdirSync(skillDir, { recursive: true });
8914
- for (const file of files) {
8915
- const filePath = path16.join(skillDir, file.name);
8916
- fs16.mkdirSync(path16.dirname(filePath), { recursive: true });
8917
- fs16.writeFileSync(filePath, file.content, "utf-8");
9369
+ fs17.mkdirSync(skillDir, { recursive: true });
9370
+ for (const file of taggedFiles) {
9371
+ const filePath = path17.join(skillDir, file.name);
9372
+ fs17.mkdirSync(path17.dirname(filePath), { recursive: true });
9373
+ fs17.writeFileSync(filePath, file.content, "utf-8");
8918
9374
  }
8919
9375
  try {
8920
9376
  execSync9(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
@@ -8926,8 +9382,12 @@ async function skillsRoutes(app) {
8926
9382
  addSkillEntry(name);
8927
9383
  addSkillPolicy(name);
8928
9384
  syncOpenClawFromPolicies(loadConfig().policies);
8929
- const unblockHash = computeSkillHash(path16.join(getSkillsDir(), name));
9385
+ const unblockHash = computeSkillHash(path17.join(getSkillsDir(), name));
8930
9386
  if (unblockHash) updateApprovedHash(name, unblockHash);
9387
+ try {
9388
+ markDownloadedAsInstalled(name);
9389
+ } catch {
9390
+ }
8931
9391
  console.log(`[Skills] Unblocked and installed skill: ${name}`);
8932
9392
  return reply.send({ success: true, message: `Skill "${name}" approved and installed` });
8933
9393
  } catch (err) {
@@ -8957,7 +9417,7 @@ async function skillsRoutes(app) {
8957
9417
  const skillMdFile = files.find((f) => /skill\.md/i.test(f.name));
8958
9418
  if (skillMdFile) {
8959
9419
  try {
8960
- const parsed = parseSkillMd2(skillMdFile.content);
9420
+ const parsed = parseSkillMd3(skillMdFile.content);
8961
9421
  if (parsed?.metadata) {
8962
9422
  parsedDescription = parsedDescription || parsed.metadata.description;
8963
9423
  parsedVersion = parsedVersion || parsed.metadata.version;
@@ -8986,10 +9446,10 @@ async function skillsRoutes(app) {
8986
9446
 
8987
9447
  // libs/shield-daemon/src/routes/exec.ts
8988
9448
  init_paths();
8989
- import * as fs17 from "node:fs";
8990
- import * as path17 from "node:path";
9449
+ import * as fs18 from "node:fs";
9450
+ import * as path18 from "node:path";
8991
9451
  function getAllowedCommandsPath2() {
8992
- return path17.join(getSystemConfigDir(), "allowed-commands.json");
9452
+ return path18.join(getSystemConfigDir(), "allowed-commands.json");
8993
9453
  }
8994
9454
  var BIN_DIRS = [
8995
9455
  "/usr/bin",
@@ -9002,22 +9462,22 @@ var binCache = null;
9002
9462
  var BIN_CACHE_TTL = 6e4;
9003
9463
  var VALID_NAME = /^[a-zA-Z0-9_-]+$/;
9004
9464
  function loadConfig2() {
9005
- if (!fs17.existsSync(getAllowedCommandsPath2())) {
9465
+ if (!fs18.existsSync(getAllowedCommandsPath2())) {
9006
9466
  return { version: "1.0.0", commands: [] };
9007
9467
  }
9008
9468
  try {
9009
- const content = fs17.readFileSync(getAllowedCommandsPath2(), "utf-8");
9469
+ const content = fs18.readFileSync(getAllowedCommandsPath2(), "utf-8");
9010
9470
  return JSON.parse(content);
9011
9471
  } catch {
9012
9472
  return { version: "1.0.0", commands: [] };
9013
9473
  }
9014
9474
  }
9015
9475
  function saveConfig2(config) {
9016
- const dir = path17.dirname(getAllowedCommandsPath2());
9017
- if (!fs17.existsSync(dir)) {
9018
- fs17.mkdirSync(dir, { recursive: true });
9476
+ const dir = path18.dirname(getAllowedCommandsPath2());
9477
+ if (!fs18.existsSync(dir)) {
9478
+ fs18.mkdirSync(dir, { recursive: true });
9019
9479
  }
9020
- fs17.writeFileSync(getAllowedCommandsPath2(), JSON.stringify(config, null, 2) + "\n", "utf-8");
9480
+ fs18.writeFileSync(getAllowedCommandsPath2(), JSON.stringify(config, null, 2) + "\n", "utf-8");
9021
9481
  }
9022
9482
  function scanSystemBins() {
9023
9483
  const pathDirs = (process.env.PATH ?? "").split(":").filter(Boolean);
@@ -9026,13 +9486,13 @@ function scanSystemBins() {
9026
9486
  const results = [];
9027
9487
  for (const dir of allDirs) {
9028
9488
  try {
9029
- if (!fs17.existsSync(dir)) continue;
9030
- const entries = fs17.readdirSync(dir);
9489
+ if (!fs18.existsSync(dir)) continue;
9490
+ const entries = fs18.readdirSync(dir);
9031
9491
  for (const entry of entries) {
9032
9492
  if (seen.has(entry)) continue;
9033
- const fullPath = path17.join(dir, entry);
9493
+ const fullPath = path18.join(dir, entry);
9034
9494
  try {
9035
- const stat = fs17.statSync(fullPath);
9495
+ const stat = fs18.statSync(fullPath);
9036
9496
  if (stat.isFile() && (stat.mode & 73) !== 0) {
9037
9497
  seen.add(entry);
9038
9498
  results.push({ name: entry, path: fullPath });
@@ -9085,7 +9545,7 @@ async function execRoutes(app) {
9085
9545
  };
9086
9546
  }
9087
9547
  for (const p of paths) {
9088
- if (!path17.isAbsolute(p)) {
9548
+ if (!path18.isAbsolute(p)) {
9089
9549
  return {
9090
9550
  success: false,
9091
9551
  error: {
@@ -9427,7 +9887,7 @@ async function authRoutes(app) {
9427
9887
  init_vault();
9428
9888
  import { isSecretEnvVar } from "@agenshield/sandbox";
9429
9889
  init_secret_sync();
9430
- import crypto5 from "node:crypto";
9890
+ import crypto6 from "node:crypto";
9431
9891
  function maskValue(value) {
9432
9892
  if (value.length <= 4) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
9433
9893
  return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" + value.slice(-4);
@@ -9522,7 +9982,7 @@ async function secretsRoutes(app) {
9522
9982
  const secrets = await vault.get("secrets") ?? [];
9523
9983
  const resolvedScope = scope ?? (policyIds?.length > 0 ? "policed" : "global");
9524
9984
  const newSecret = {
9525
- id: crypto5.randomUUID(),
9985
+ id: crypto6.randomUUID(),
9526
9986
  name: name.trim(),
9527
9987
  value,
9528
9988
  policyIds: resolvedScope === "standalone" ? [] : policyIds ?? [],
@@ -9581,18 +10041,18 @@ async function secretsRoutes(app) {
9581
10041
  }
9582
10042
 
9583
10043
  // libs/shield-daemon/src/routes/fs.ts
9584
- import * as fs18 from "node:fs";
9585
- import * as path18 from "node:path";
10044
+ import * as fs19 from "node:fs";
10045
+ import * as path19 from "node:path";
9586
10046
  import * as os6 from "node:os";
9587
10047
  var MAX_ENTRIES = 200;
9588
10048
  async function fsRoutes(app) {
9589
10049
  app.get("/fs/browse", async (request2) => {
9590
10050
  const dirPath = request2.query.path || os6.homedir();
9591
10051
  const showHidden = request2.query.showHidden === "true";
9592
- const resolvedPath = path18.resolve(dirPath);
10052
+ const resolvedPath = path19.resolve(dirPath);
9593
10053
  let dirents;
9594
10054
  try {
9595
- dirents = fs18.readdirSync(resolvedPath, { withFileTypes: true });
10055
+ dirents = fs19.readdirSync(resolvedPath, { withFileTypes: true });
9596
10056
  } catch {
9597
10057
  return { success: true, data: { entries: [] } };
9598
10058
  }
@@ -9601,7 +10061,7 @@ async function fsRoutes(app) {
9601
10061
  if (!showHidden && dirent.name.startsWith(".")) continue;
9602
10062
  entries.push({
9603
10063
  name: dirent.name,
9604
- path: path18.join(resolvedPath, dirent.name),
10064
+ path: path19.join(resolvedPath, dirent.name),
9605
10065
  type: dirent.isDirectory() ? "directory" : "file"
9606
10066
  });
9607
10067
  if (entries.length >= MAX_ENTRIES) break;
@@ -9615,8 +10075,8 @@ async function fsRoutes(app) {
9615
10075
  }
9616
10076
 
9617
10077
  // libs/shield-daemon/src/services/activity-log.ts
9618
- import * as fs19 from "node:fs";
9619
- import * as path19 from "node:path";
10078
+ import * as fs20 from "node:fs";
10079
+ import * as path20 from "node:path";
9620
10080
  init_paths();
9621
10081
  var ACTIVITY_FILE = "activity.jsonl";
9622
10082
  var MAX_SIZE_BYTES = 100 * 1024 * 1024;
@@ -9634,12 +10094,12 @@ var ActivityLog = class {
9634
10094
  writeCount = 0;
9635
10095
  unsubscribe;
9636
10096
  constructor() {
9637
- this.filePath = path19.join(getConfigDir(), ACTIVITY_FILE);
10097
+ this.filePath = path20.join(getConfigDir(), ACTIVITY_FILE);
9638
10098
  }
9639
10099
  /** Read historical events from the JSONL file, newest first */
9640
10100
  getHistory(limit = 500) {
9641
- if (!fs19.existsSync(this.filePath)) return [];
9642
- const content = fs19.readFileSync(this.filePath, "utf-8");
10101
+ if (!fs20.existsSync(this.filePath)) return [];
10102
+ const content = fs20.readFileSync(this.filePath, "utf-8");
9643
10103
  const lines = content.split("\n").filter(Boolean);
9644
10104
  const events = [];
9645
10105
  for (const line of lines) {
@@ -9664,7 +10124,7 @@ var ActivityLog = class {
9664
10124
  }
9665
10125
  append(event) {
9666
10126
  const line = JSON.stringify(event) + "\n";
9667
- fs19.appendFileSync(this.filePath, line, "utf-8");
10127
+ fs20.appendFileSync(this.filePath, line, "utf-8");
9668
10128
  this.writeCount++;
9669
10129
  if (this.writeCount % PRUNE_INTERVAL === 0) {
9670
10130
  this.rotate();
@@ -9672,7 +10132,7 @@ var ActivityLog = class {
9672
10132
  }
9673
10133
  rotate() {
9674
10134
  try {
9675
- const stat = fs19.statSync(this.filePath);
10135
+ const stat = fs20.statSync(this.filePath);
9676
10136
  if (stat.size > MAX_SIZE_BYTES) {
9677
10137
  this.truncateBySize();
9678
10138
  }
@@ -9682,15 +10142,15 @@ var ActivityLog = class {
9682
10142
  }
9683
10143
  /** Keep newest half of lines when file exceeds size limit */
9684
10144
  truncateBySize() {
9685
- const content = fs19.readFileSync(this.filePath, "utf-8");
10145
+ const content = fs20.readFileSync(this.filePath, "utf-8");
9686
10146
  const lines = content.split("\n").filter(Boolean);
9687
10147
  const keep = lines.slice(Math.floor(lines.length / 2));
9688
- fs19.writeFileSync(this.filePath, keep.join("\n") + "\n", "utf-8");
10148
+ fs20.writeFileSync(this.filePath, keep.join("\n") + "\n", "utf-8");
9689
10149
  }
9690
10150
  /** Remove entries older than 24 hours */
9691
10151
  pruneOldEntries() {
9692
- if (!fs19.existsSync(this.filePath)) return;
9693
- const content = fs19.readFileSync(this.filePath, "utf-8");
10152
+ if (!fs20.existsSync(this.filePath)) return;
10153
+ const content = fs20.readFileSync(this.filePath, "utf-8");
9694
10154
  const lines = content.split("\n").filter(Boolean);
9695
10155
  const cutoff = Date.now() - MAX_AGE_MS;
9696
10156
  const kept = lines.filter((line) => {
@@ -9702,7 +10162,7 @@ var ActivityLog = class {
9702
10162
  }
9703
10163
  });
9704
10164
  if (kept.length < lines.length) {
9705
- fs19.writeFileSync(this.filePath, kept.join("\n") + "\n", "utf-8");
10165
+ fs20.writeFileSync(this.filePath, kept.join("\n") + "\n", "utf-8");
9706
10166
  }
9707
10167
  }
9708
10168
  };
@@ -9823,7 +10283,7 @@ async function openclawRoutes(app) {
9823
10283
  }
9824
10284
 
9825
10285
  // libs/shield-daemon/src/routes/rpc.ts
9826
- import * as crypto6 from "node:crypto";
10286
+ import * as crypto7 from "node:crypto";
9827
10287
  import * as nodefs from "node:fs";
9828
10288
 
9829
10289
  // libs/shield-daemon/src/policy/url-matcher.ts
@@ -9843,11 +10303,11 @@ function normalizeUrlTarget(url) {
9843
10303
  const trimmed = url.trim();
9844
10304
  try {
9845
10305
  const parsed = new URL(trimmed);
9846
- let path21 = parsed.pathname;
9847
- if (path21.length > 1) {
9848
- path21 = path21.replace(/\/+$/, "");
10306
+ let path24 = parsed.pathname;
10307
+ if (path24.length > 1) {
10308
+ path24 = path24.replace(/\/+$/, "");
9849
10309
  }
9850
- return `${parsed.protocol}//${parsed.host}${path21}${parsed.search}`;
10310
+ return `${parsed.protocol}//${parsed.host}${path24}${parsed.search}`;
9851
10311
  } catch {
9852
10312
  return trimmed.replace(/\/+$/, "");
9853
10313
  }
@@ -9937,7 +10397,7 @@ function checkUrlPolicy(policies, url) {
9937
10397
  // libs/shield-daemon/src/proxy/server.ts
9938
10398
  import * as http from "node:http";
9939
10399
  import * as net from "node:net";
9940
- function createPerRunProxy(urlPolicies, onActivity, logger) {
10400
+ function createPerRunProxy(urlPolicies, onActivity, logger, onBlock) {
9941
10401
  const server = http.createServer((req, res) => {
9942
10402
  onActivity();
9943
10403
  const url = req.url;
@@ -9949,6 +10409,7 @@ function createPerRunProxy(urlPolicies, onActivity, logger) {
9949
10409
  const allowed = checkUrlPolicy(urlPolicies, url);
9950
10410
  if (!allowed) {
9951
10411
  logger(`BLOCKED HTTP ${req.method} ${url}`);
10412
+ onBlock?.(req.method || "GET", url, "http");
9952
10413
  res.writeHead(403, { "Content-Type": "text/plain" });
9953
10414
  res.end("Blocked by AgenShield URL policy");
9954
10415
  return;
@@ -9992,6 +10453,7 @@ function createPerRunProxy(urlPolicies, onActivity, logger) {
9992
10453
  const allowed = checkUrlPolicy(urlPolicies, `https://${hostname2}`);
9993
10454
  if (!allowed) {
9994
10455
  logger(`BLOCKED CONNECT ${hostname2}:${port}`);
10456
+ onBlock?.("CONNECT", `${hostname2}:${port}`, "https");
9995
10457
  clientSocket.write("HTTP/1.1 403 Forbidden\r\n\r\n");
9996
10458
  clientSocket.destroy();
9997
10459
  return;
@@ -10054,7 +10516,16 @@ var ProxyPool = class {
10054
10516
  const logger = (msg) => {
10055
10517
  console.log(`[proxy:${execId.slice(0, 8)}] ${msg}`);
10056
10518
  };
10057
- const server = createPerRunProxy(urlPolicies, onActivity, logger);
10519
+ const onBlock = (method, target, protocol) => {
10520
+ emitInterceptorEvent({
10521
+ type: "denied",
10522
+ operation: "http_request",
10523
+ target: protocol === "https" ? `https://${target}` : target,
10524
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10525
+ error: `Blocked by URL policy (${method})`
10526
+ });
10527
+ };
10528
+ const server = createPerRunProxy(urlPolicies, onActivity, logger, onBlock);
10058
10529
  const port = await new Promise((resolve3, reject) => {
10059
10530
  server.listen(0, "127.0.0.1", () => {
10060
10531
  const addr = server.address();
@@ -10133,16 +10604,23 @@ function matchCommandPattern(pattern, target) {
10133
10604
  const firstSpace = target.indexOf(" ");
10134
10605
  const cmd = firstSpace >= 0 ? target.slice(0, firstSpace) : target;
10135
10606
  if (cmd.startsWith("/")) {
10136
- const basename3 = cmd.split("/").pop() || cmd;
10137
- normalizedTarget = firstSpace >= 0 ? basename3 + target.slice(firstSpace) : basename3;
10607
+ const basename6 = cmd.split("/").pop() || cmd;
10608
+ normalizedTarget = firstSpace >= 0 ? basename6 + target.slice(firstSpace) : basename6;
10138
10609
  }
10139
10610
  if (trimmed.endsWith(":*")) {
10140
- const prefix = trimmed.slice(0, -2);
10611
+ let prefix = trimmed.slice(0, -2);
10612
+ if (prefix.includes("/")) {
10613
+ prefix = prefix.split("/").pop() || prefix;
10614
+ }
10141
10615
  const lowerTarget = normalizedTarget.toLowerCase();
10142
10616
  const lowerPrefix = prefix.toLowerCase();
10143
10617
  return lowerTarget === lowerPrefix || lowerTarget.startsWith(lowerPrefix + " ");
10144
10618
  }
10145
- return normalizedTarget.toLowerCase() === trimmed.toLowerCase();
10619
+ let normalizedPattern = trimmed;
10620
+ if (trimmed.includes("/")) {
10621
+ normalizedPattern = trimmed.split("/").pop() || trimmed;
10622
+ }
10623
+ return normalizedTarget.toLowerCase() === normalizedPattern.toLowerCase();
10146
10624
  }
10147
10625
  function operationToTarget(operation) {
10148
10626
  switch (operation) {
@@ -10185,8 +10663,8 @@ function determineNetworkAccess(_config, matchedPolicy, target) {
10185
10663
  if (matchedPolicy?.networkAccess) return matchedPolicy.networkAccess;
10186
10664
  const cleanTarget = target.startsWith("fork:") ? target.slice(5) : target;
10187
10665
  const cmdPart = cleanTarget.split(" ")[0] || "";
10188
- const basename3 = cmdPart.includes("/") ? cmdPart.split("/").pop() : cmdPart;
10189
- if (!NETWORK_COMMANDS.has(basename3.toLowerCase())) return "none";
10666
+ const basename6 = cmdPart.includes("/") ? cmdPart.split("/").pop() : cmdPart;
10667
+ if (!NETWORK_COMMANDS.has(basename6.toLowerCase())) return "none";
10190
10668
  return "proxy";
10191
10669
  }
10192
10670
  async function buildSandboxConfig(config, matchedPolicy, _context, target) {
@@ -10235,7 +10713,7 @@ async function buildSandboxConfig(config, matchedPolicy, _context, target) {
10235
10713
  console.log(`[sandbox] direct network access (no proxy)`);
10236
10714
  sandbox.networkAllowed = true;
10237
10715
  } else if (networkMode === "proxy") {
10238
- const execId = crypto6.randomUUID();
10716
+ const execId = crypto7.randomUUID();
10239
10717
  const commandBasename = extractCommandBasename(target || "");
10240
10718
  const urlPolicies = filterUrlPoliciesForCommand(config.policies || [], commandBasename);
10241
10719
  const pool = getProxyPool();
@@ -10300,7 +10778,11 @@ async function evaluatePolicyCheck(operation, target, context) {
10300
10778
  } else if (targetType === "command") {
10301
10779
  matches = matchCommandPattern(pattern, effectiveTarget);
10302
10780
  } else {
10303
- const regex = globToRegex(pattern);
10781
+ let fsPattern = pattern;
10782
+ if (targetType === "filesystem" && fsPattern.endsWith("/")) {
10783
+ fsPattern = fsPattern + "**";
10784
+ }
10785
+ const regex = globToRegex(fsPattern);
10304
10786
  matches = regex.test(effectiveTarget);
10305
10787
  }
10306
10788
  console.log("[policy_check] pattern:", pattern, "-> base:", targetType === "url" ? normalizeUrlBase(pattern) : pattern, "| target:", effectiveTarget, "| matches:", matches);
@@ -10366,12 +10848,29 @@ async function handleHttpRequest(params) {
10366
10848
  body: responseBody
10367
10849
  };
10368
10850
  }
10851
+ async function handlePolicyCheck(params) {
10852
+ const operation = String(params["operation"] ?? "");
10853
+ const target = String(params["target"] ?? "");
10854
+ const context = params["context"];
10855
+ const result = await evaluatePolicyCheck(operation, target, context);
10856
+ if (!result.allowed) {
10857
+ if (operation === "exec") {
10858
+ emitExecDenied(target, result.reason || "Denied by policy");
10859
+ } else {
10860
+ emitInterceptorEvent({
10861
+ type: "denied",
10862
+ operation,
10863
+ target,
10864
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10865
+ policyId: result.policyId,
10866
+ error: result.reason || "Denied by policy"
10867
+ });
10868
+ }
10869
+ }
10870
+ return result;
10871
+ }
10369
10872
  var handlers = {
10370
- policy_check: (params) => evaluatePolicyCheck(
10371
- String(params["operation"] ?? ""),
10372
- String(params["target"] ?? ""),
10373
- params["context"]
10374
- ),
10873
+ policy_check: (params) => handlePolicyCheck(params),
10375
10874
  events_batch: (params) => handleEventsBatch(params),
10376
10875
  http_request: (params) => handleHttpRequest(params),
10377
10876
  ping: () => ({ status: "ok" })
@@ -10487,22 +10986,22 @@ async function registerRoutes(app) {
10487
10986
  }
10488
10987
 
10489
10988
  // libs/shield-daemon/src/static.ts
10490
- import * as fs20 from "node:fs";
10491
- import * as path20 from "node:path";
10989
+ import * as fs21 from "node:fs";
10990
+ import * as path21 from "node:path";
10492
10991
  import { fileURLToPath as fileURLToPath2 } from "node:url";
10493
10992
  var __filename = fileURLToPath2(import.meta.url);
10494
- var __dirname = path20.dirname(__filename);
10993
+ var __dirname = path21.dirname(__filename);
10495
10994
  function getUiAssetsPath() {
10496
- const pkgRootPath = path20.join(__dirname, "..", "ui-assets");
10497
- if (fs20.existsSync(pkgRootPath)) {
10995
+ const pkgRootPath = path21.join(__dirname, "..", "ui-assets");
10996
+ if (fs21.existsSync(pkgRootPath)) {
10498
10997
  return pkgRootPath;
10499
10998
  }
10500
- const bundledPath = path20.join(__dirname, "ui-assets");
10501
- if (fs20.existsSync(bundledPath)) {
10999
+ const bundledPath = path21.join(__dirname, "ui-assets");
11000
+ if (fs21.existsSync(bundledPath)) {
10502
11001
  return bundledPath;
10503
11002
  }
10504
- const devPath = path20.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
10505
- if (fs20.existsSync(devPath)) {
11003
+ const devPath = path21.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
11004
+ if (fs21.existsSync(devPath)) {
10506
11005
  return devPath;
10507
11006
  }
10508
11007
  return null;
@@ -10592,10 +11091,16 @@ async function startServer(config) {
10592
11091
  startSecurityWatcher(1e4);
10593
11092
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
10594
11093
  const skillsDir2 = `${agentHome}/.openclaw/skills`;
10595
- if (!fs21.existsSync(skillsDir2)) {
10596
- fs21.mkdirSync(skillsDir2, { recursive: true, mode: 493 });
11094
+ if (!fs23.existsSync(skillsDir2)) {
11095
+ fs23.mkdirSync(skillsDir2, { recursive: true, mode: 493 });
10597
11096
  console.log(`[Daemon] Created skills directory: ${skillsDir2}`);
10598
11097
  }
11098
+ try {
11099
+ await getInstallationKey();
11100
+ console.log("[Daemon] Installation key ready");
11101
+ } catch (err) {
11102
+ console.warn("[Daemon] Failed to initialize installation key:", err.message);
11103
+ }
10599
11104
  startSkillsWatcher(skillsDir2, {
10600
11105
  onUntrustedDetected: (info) => emitSkillUntrustedDetected(info.name, info.reason),
10601
11106
  onApproved: (name) => emitSkillApproved(name)