runwork 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +423 -108
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7381,7 +7381,7 @@ function createKeyboardListener() {
7381
7381
  }
7382
7382
 
7383
7383
  // src/generated/version.ts
7384
- var VERSION = "0.16.0";
7384
+ var VERSION = "0.17.0";
7385
7385
 
7386
7386
  // src/commands/dev.ts
7387
7387
  var exports_dev = {};
@@ -10989,6 +10989,92 @@ function extractCodexRolloutSession(raw, agentSlug) {
10989
10989
  digest.project = "codex";
10990
10990
  return digest;
10991
10991
  }
10992
+ function extractCursorSessions(composerRows, bubbleRows, sinceISO) {
10993
+ const composerCreatedAt = new Map;
10994
+ for (const line of composerRows.split(`
10995
+ `)) {
10996
+ if (!line.trim())
10997
+ continue;
10998
+ try {
10999
+ const o = JSON.parse(line);
11000
+ if (o.id)
11001
+ composerCreatedAt.set(o.id, typeof o.createdAt === "number" ? o.createdAt : 0);
11002
+ } catch {}
11003
+ }
11004
+ const byComposer = new Map;
11005
+ for (const line of bubbleRows.split(`
11006
+ `)) {
11007
+ if (!line.trim())
11008
+ continue;
11009
+ let o;
11010
+ try {
11011
+ o = JSON.parse(line);
11012
+ } catch {
11013
+ continue;
11014
+ }
11015
+ const parts = (o.k ?? "").split(":");
11016
+ if (parts.length < 3)
11017
+ continue;
11018
+ const cid = parts[1];
11019
+ const list = byComposer.get(cid) ?? [];
11020
+ list.push({ type: o.type ?? null, text: o.text ?? null, ts: o.ts ?? null, tool: o.tool ?? null, status: o.status ?? null });
11021
+ byComposer.set(cid, list);
11022
+ }
11023
+ const digests = [];
11024
+ for (const [cid, bubbles] of byComposer) {
11025
+ bubbles.sort((a, b) => (a.ts ?? "").localeCompare(b.ts ?? ""));
11026
+ const digest = {
11027
+ agentSlug: "cursor",
11028
+ project: "cursor",
11029
+ start: null,
11030
+ end: null,
11031
+ userMessages: [],
11032
+ droppedUserMessages: 0,
11033
+ toolCounts: {},
11034
+ skillInvocations: [],
11035
+ errorCount: 0,
11036
+ assistantTurns: 0
11037
+ };
11038
+ for (const b of bubbles) {
11039
+ if (b.ts) {
11040
+ if (!digest.start)
11041
+ digest.start = b.ts;
11042
+ digest.end = b.ts;
11043
+ }
11044
+ if (b.tool) {
11045
+ digest.toolCounts[b.tool] = (digest.toolCounts[b.tool] ?? 0) + 1;
11046
+ if (b.status === "error")
11047
+ digest.errorCount++;
11048
+ } else if (b.type === 2) {
11049
+ digest.assistantTurns++;
11050
+ }
11051
+ if (b.type === 1 && typeof b.text === "string") {
11052
+ const trimmed = b.text.trim();
11053
+ if (!trimmed || SKIP_PREFIXES.some((p) => trimmed.startsWith(p)))
11054
+ continue;
11055
+ if (digest.userMessages.length >= MAX_MSGS_PER_SESSION) {
11056
+ digest.droppedUserMessages++;
11057
+ continue;
11058
+ }
11059
+ digest.userMessages.push(trimmed.length > MAX_MSG_CHARS ? trimmed.slice(0, MAX_MSG_CHARS) + " [...]" : trimmed);
11060
+ }
11061
+ }
11062
+ if (!digest.start) {
11063
+ const ms = composerCreatedAt.get(cid);
11064
+ if (ms) {
11065
+ const iso = new Date(ms).toISOString();
11066
+ digest.start = iso;
11067
+ digest.end = iso;
11068
+ }
11069
+ }
11070
+ if (digest.userMessages.length === 0)
11071
+ continue;
11072
+ if (sinceISO && digest.end && digest.end < sinceISO)
11073
+ continue;
11074
+ digests.push(digest);
11075
+ }
11076
+ return digests;
11077
+ }
10992
11078
  function formatCombinedDigest(sessions, opts = { days: 7 }) {
10993
11079
  const sorted = [...sessions].sort((a, b) => (a.start ?? "").localeCompare(b.start ?? ""));
10994
11080
  const lines = [];
@@ -11032,6 +11118,33 @@ function formatCombinedDigest(sessions, opts = { days: 7 }) {
11032
11118
  // src/agents/utils/json-config.ts
11033
11119
  import { readFileSync as readFileSync22, writeFileSync as writeFileSync14, mkdirSync as mkdirSync13, existsSync as existsSync25 } from "fs";
11034
11120
  import { dirname as dirname5 } from "path";
11121
+
11122
+ // src/sync/hash.ts
11123
+ import { createHash as createHash2 } from "crypto";
11124
+ function contentHash(content) {
11125
+ return "sha256:" + createHash2("sha256").update(content).digest("hex");
11126
+ }
11127
+ function stableStringify(value) {
11128
+ return JSON.stringify(sortValue(value));
11129
+ }
11130
+ function sortValue(value) {
11131
+ if (Array.isArray(value))
11132
+ return value.map(sortValue);
11133
+ if (value !== null && typeof value === "object") {
11134
+ const obj = value;
11135
+ const sorted = {};
11136
+ for (const key of Object.keys(obj).sort()) {
11137
+ sorted[key] = sortValue(obj[key]);
11138
+ }
11139
+ return sorted;
11140
+ }
11141
+ return value;
11142
+ }
11143
+ function configHash(value) {
11144
+ return contentHash(stableStringify(value));
11145
+ }
11146
+
11147
+ // src/agents/utils/json-config.ts
11035
11148
  function isRunworkManagedKey(key) {
11036
11149
  return key === RUNWORK_WORKSPACE_MCP_NAME || key.startsWith(RUNWORK_MCP_PREFIX) || key.startsWith(RUNWORK_MCP_PREFIX_LEGACY);
11037
11150
  }
@@ -11070,6 +11183,14 @@ function removeRunworkMcpServers(filePath, topKey) {
11070
11183
  function mergeJsonMcpServers(filePath, servers, topKey) {
11071
11184
  const config = readJsonConfig(filePath);
11072
11185
  const existing = config[topKey] || {};
11186
+ const managedBefore = {};
11187
+ for (const key of Object.keys(existing)) {
11188
+ if (isRunworkManagedKey(key))
11189
+ managedBefore[key] = existing[key];
11190
+ }
11191
+ const changed = configHash(managedBefore) !== configHash(servers);
11192
+ if (!changed)
11193
+ return false;
11073
11194
  for (const key of Object.keys(existing)) {
11074
11195
  if (isRunworkManagedKey(key)) {
11075
11196
  delete existing[key];
@@ -11078,6 +11199,7 @@ function mergeJsonMcpServers(filePath, servers, topKey) {
11078
11199
  Object.assign(existing, servers);
11079
11200
  config[topKey] = existing;
11080
11201
  writeJsonConfig(filePath, config);
11202
+ return true;
11081
11203
  }
11082
11204
 
11083
11205
  // src/agents/utils/instruction-hint.ts
@@ -11401,6 +11523,19 @@ function resolveAgentDefaults(input) {
11401
11523
  };
11402
11524
  }
11403
11525
 
11526
+ // src/ui/verbosity.ts
11527
+ var verbose = false;
11528
+ function setVerbose(value) {
11529
+ verbose = value;
11530
+ }
11531
+ function isVerbose() {
11532
+ return verbose;
11533
+ }
11534
+ function vlog(...args) {
11535
+ if (verbose)
11536
+ console.log(...args);
11537
+ }
11538
+
11404
11539
  // src/agents/claude-code.ts
11405
11540
  var PLUGIN_NAME = "runwork";
11406
11541
  var PLUGIN_VERSION = "1.0.0";
@@ -11503,7 +11638,7 @@ class ClaudeCodeAdapter {
11503
11638
  ]
11504
11639
  };
11505
11640
  writeFileSync16(join21(hooksDir, "hooks.json"), JSON.stringify(hooksManifest, null, 2));
11506
- console.log(` [Claude Code] Installed SessionStart hook (${label}) -> ${scriptPath}`);
11641
+ vlog(` [Claude Code] Installed SessionStart hook (${label}) -> ${scriptPath}`);
11507
11642
  }
11508
11643
  async writeSkills(skills, scope) {
11509
11644
  const baseDir = scope === "project" ? join21(process.cwd(), ".claude", "skills") : join21(homedir5(), ".claude", "skills");
@@ -11536,6 +11671,7 @@ class ClaudeCodeAdapter {
11536
11671
  }
11537
11672
  this.registerPlugin(pluginDir);
11538
11673
  }
11674
+ return skills.length;
11539
11675
  }
11540
11676
  async writeBuiltInHooks(scope) {
11541
11677
  if (scope !== "user")
@@ -12030,7 +12166,7 @@ ${instructions}`;
12030
12166
  lastUpdated: new Date().toISOString()
12031
12167
  };
12032
12168
  writeJsonConfig(marketplacesPath, marketplaces);
12033
- console.log(` [Claude Code] ${wasRegistered ? "Refreshed" : "Registered"} plugin ` + `${pluginKey} v${PLUGIN_VERSION} -> ${installedPath}`);
12169
+ vlog(` [Claude Code] ${wasRegistered ? "Refreshed" : "Registered"} plugin ` + `${pluginKey} v${PLUGIN_VERSION} -> ${installedPath}`);
12034
12170
  const settingsPath = join21(homedir5(), ".claude", "settings.json");
12035
12171
  const settings = readJsonConfig(settingsPath);
12036
12172
  if (!settings["enabledPlugins"]) {
@@ -12330,12 +12466,12 @@ class ClaudeDesktopAdapter {
12330
12466
  if (rpm) {
12331
12467
  writePluginMetadata(rpm.pluginPath, getPluginMetadata());
12332
12468
  writePluginSkills(rpm.pluginPath, skills);
12333
- return;
12469
+ return skills.length;
12334
12470
  }
12335
12471
  const pluginsDir = findCoworkPluginsDir();
12336
12472
  if (!pluginsDir) {
12337
12473
  console.warn(" [Claude Desktop] Cowork plugins directory not found. Open Claude Desktop at least once first.");
12338
- return;
12474
+ return 0;
12339
12475
  }
12340
12476
  const cacheDir = join23(pluginsDir, "cache", "runwork", PLUGIN_NAME2, PLUGIN_VERSION2);
12341
12477
  const marketRoot = join23(pluginsDir, "marketplaces", "runwork");
@@ -12370,6 +12506,7 @@ class ClaudeDesktopAdapter {
12370
12506
  };
12371
12507
  writeJsonConfig(marketplacesPath, marketplaces);
12372
12508
  setCoworkPluginEnabled(pluginsDir, true);
12509
+ return skills.length;
12373
12510
  }
12374
12511
  async writeInstructionHint(hint, _scope) {
12375
12512
  for (const path2 of getCoworkMemoryClaudeMdPaths()) {
@@ -12780,6 +12917,9 @@ async function loadAdapter() {
12780
12917
  return null;
12781
12918
  }
12782
12919
  adapter = await loadAdapter();
12920
+ function sqliteAvailable() {
12921
+ return adapter !== null;
12922
+ }
12783
12923
  var BUSY_TIMEOUT_MS = 5000;
12784
12924
  function setBusyTimeout(db) {
12785
12925
  try {
@@ -12857,7 +12997,7 @@ class CursorAdapter {
12857
12997
  }
12858
12998
  async writeSkills(skills, scope) {
12859
12999
  if (scope === "user")
12860
- return;
13000
+ return 0;
12861
13001
  const rulesDir = join24(process.cwd(), ".cursor", "rules");
12862
13002
  mkdirSync18(rulesDir, { recursive: true });
12863
13003
  for (const skill of skills) {
@@ -12869,6 +13009,7 @@ alwaysApply: false
12869
13009
  ${skill.content}`;
12870
13010
  writeFileSync19(join24(rulesDir, `${skill.filename}.mdc`), mdcContent);
12871
13011
  }
13012
+ return skills.length;
12872
13013
  }
12873
13014
  async writeInstructionHint(hint, scope) {
12874
13015
  const filePath = scope === "project" ? join24(process.cwd(), ".cursor", "rules", "runwork.mdc") : join24(homedir7(), ".cursor", "rules", "runwork.mdc");
@@ -12898,15 +13039,7 @@ ${instructions}`;
12898
13039
  if (config.networkAllowlist?.length) {
12899
13040
  this.mergeSandboxAllowlist(config.networkAllowlist);
12900
13041
  }
12901
- const os2 = platform4();
12902
- let globalDbPath;
12903
- if (os2 === "darwin") {
12904
- globalDbPath = join24(homedir7(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
12905
- } else if (os2 === "win32") {
12906
- globalDbPath = join24(process.env.APPDATA || join24(homedir7(), "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb");
12907
- } else {
12908
- globalDbPath = join24(homedir7(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
12909
- }
13042
+ const globalDbPath = this.globalStorageDbPath();
12910
13043
  if (!existsSync30(globalDbPath))
12911
13044
  return;
12912
13045
  const db = openWritableSqlite(globalDbPath);
@@ -12986,15 +13119,7 @@ ${instructions}`;
12986
13119
  async readUsageStats(lastSyncAt) {
12987
13120
  try {
12988
13121
  const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
12989
- const os2 = platform4();
12990
- let globalDbPath;
12991
- if (os2 === "darwin") {
12992
- globalDbPath = join24(homedir7(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
12993
- } else if (os2 === "win32") {
12994
- globalDbPath = join24(process.env.APPDATA || join24(homedir7(), "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb");
12995
- } else {
12996
- globalDbPath = join24(homedir7(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
12997
- }
13122
+ const globalDbPath = this.globalStorageDbPath();
12998
13123
  let sessionCount = 0;
12999
13124
  let totalLinesAdded = 0;
13000
13125
  let totalLinesRemoved = 0;
@@ -13069,6 +13194,30 @@ ${instructions}`;
13069
13194
  return null;
13070
13195
  }
13071
13196
  }
13197
+ globalStorageDbPath() {
13198
+ const os2 = platform4();
13199
+ if (os2 === "darwin") {
13200
+ return join24(homedir7(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
13201
+ }
13202
+ if (os2 === "win32") {
13203
+ return join24(process.env.APPDATA || join24(homedir7(), "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb");
13204
+ }
13205
+ return join24(homedir7(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
13206
+ }
13207
+ async readSessionDigests(sinceISO) {
13208
+ try {
13209
+ const dbPath = this.globalStorageDbPath();
13210
+ if (!existsSync30(dbPath) || !sqliteAvailable())
13211
+ return null;
13212
+ const composerRows = queryReadonlySqlite(dbPath, `SELECT json_object('id', json_extract(value,'$.composerId'), 'createdAt', json_extract(value,'$.createdAt')) FROM cursorDiskKV WHERE key LIKE 'composerData:%'`);
13213
+ const bubbleRows = queryReadonlySqlite(dbPath, `SELECT json_object('k', key, 'type', json_extract(value,'$.type'), 'text', substr(json_extract(value,'$.text'),1,2000), 'ts', json_extract(value,'$.createdAt'), 'tool', json_extract(value,'$.toolFormerData.name'), 'status', json_extract(value,'$.toolFormerData.status')) FROM cursorDiskKV WHERE key LIKE 'bubbleId:%'`);
13214
+ if (!bubbleRows.trim())
13215
+ return [];
13216
+ return extractCursorSessions(composerRows, bubbleRows, sinceISO);
13217
+ } catch {
13218
+ return null;
13219
+ }
13220
+ }
13072
13221
  }
13073
13222
 
13074
13223
  // src/agents/windsurf.ts
@@ -13113,7 +13262,7 @@ class WindsurfAdapter {
13113
13262
  }
13114
13263
  async writeSkills(skills, scope) {
13115
13264
  if (scope === "user")
13116
- return;
13265
+ return 0;
13117
13266
  const rulesDir = join25(process.cwd(), ".windsurf", "rules");
13118
13267
  mkdirSync19(rulesDir, { recursive: true });
13119
13268
  for (const skill of skills) {
@@ -13124,6 +13273,7 @@ trigger: manual
13124
13273
  ${skill.content}`;
13125
13274
  writeFileSync20(join25(rulesDir, `${skill.filename}.md`), content);
13126
13275
  }
13276
+ return skills.length;
13127
13277
  }
13128
13278
  async writeTeamInstructions(instructions, scope) {
13129
13279
  const filePath = scope === "project" ? join25(process.cwd(), ".windsurf", "rules", "runwork-team.md") : join25(getWindsurfDataDir(), "rules", "runwork-team.md");
@@ -13371,7 +13521,7 @@ var AGENT_REGISTRY = [
13371
13521
  ]
13372
13522
  },
13373
13523
  launch: { app: { macos: "Codex", windows: "Codex" } },
13374
- logo: "openai",
13524
+ logo: "codex",
13375
13525
  downloadUrl: "https://openai.com/codex/",
13376
13526
  skillsPaths: { global: ".codex/skills", project: ".agents/skills" },
13377
13527
  instructionFile: { global: ".codex/instructions.md", project: "AGENTS.md" },
@@ -13396,7 +13546,7 @@ var AGENT_REGISTRY = [
13396
13546
  category: "cli",
13397
13547
  detection: { method: "binary", target: "codex" },
13398
13548
  launch: { cli: "codex", cliAcceptsPrompt: true },
13399
- logo: "openai",
13549
+ logo: "codex",
13400
13550
  downloadUrl: "https://github.com/openai/codex",
13401
13551
  skillsPaths: { global: ".codex/skills", project: ".agents/skills" },
13402
13552
  instructionFile: { global: ".codex/instructions.md", project: "AGENTS.md" },
@@ -13919,6 +14069,7 @@ class CodexAdapter {
13919
14069
  mkdirSync20(skillDir, { recursive: true });
13920
14070
  writeFileSync21(join28(skillDir, "SKILL.md"), buildSkillMd2(skill));
13921
14071
  }
14072
+ return skills.length;
13922
14073
  }
13923
14074
  async writeInstructionHint(hint, scope) {
13924
14075
  const filePath = scope === "project" ? join28(process.cwd(), "AGENTS.md") : join28(homedir11(), ".codex", "AGENTS.md");
@@ -14280,12 +14431,13 @@ class ClineAdapter {
14280
14431
  }
14281
14432
  async writeSkills(skills, scope) {
14282
14433
  if (scope === "user")
14283
- return;
14434
+ return 0;
14284
14435
  const rulesDir = join29(process.cwd(), ".clinerules");
14285
14436
  mkdirSync21(rulesDir, { recursive: true });
14286
14437
  for (const skill of skills) {
14287
14438
  writeFileSync22(join29(rulesDir, `${skill.filename}.md`), buildSkillMd2(skill));
14288
14439
  }
14440
+ return skills.length;
14289
14441
  }
14290
14442
  async writeInstructionHint(hint, scope) {
14291
14443
  if (scope === "user")
@@ -14410,6 +14562,7 @@ class GeminiAdapter {
14410
14562
  mkdirSync22(skillDir, { recursive: true });
14411
14563
  writeFileSync23(join30(skillDir, "SKILL.md"), buildSkillMd2(skill));
14412
14564
  }
14565
+ return skills.length;
14413
14566
  }
14414
14567
  async writeInstructionHint(hint, scope) {
14415
14568
  const filePath = scope === "project" ? join30(process.cwd(), "GEMINI.md") : join30(homedir13(), ".gemini", "GEMINI.md");
@@ -14509,13 +14662,13 @@ class GenericAgentAdapter {
14509
14662
  }
14510
14663
  async writeSkills(skills, scope) {
14511
14664
  if (!this.def.skillsPaths)
14512
- return;
14665
+ return 0;
14513
14666
  const pathTemplate = scope === "project" ? this.def.skillsPaths.project : this.def.skillsPaths.global;
14514
14667
  if (!pathTemplate)
14515
- return;
14668
+ return 0;
14516
14669
  const baseDir = resolveToAbsolute(pathTemplate, scope === "project" ? "project" : "global");
14517
14670
  if (!baseDir)
14518
- return;
14671
+ return 0;
14519
14672
  for (const skill of skills) {
14520
14673
  if (skill.name !== skill.filename) {
14521
14674
  const oldDir = join31(baseDir, skill.name);
@@ -14529,6 +14682,7 @@ class GenericAgentAdapter {
14529
14682
  mkdirSync23(skillDir, { recursive: true });
14530
14683
  writeFileSync24(join31(skillDir, "SKILL.md"), buildSkillMd2(skill));
14531
14684
  }
14685
+ return skills.length;
14532
14686
  }
14533
14687
  async writeInstructionHint(hint, scope) {
14534
14688
  if (!this.def.instructionFile)
@@ -14643,9 +14797,9 @@ function printNoAgentsMessage() {
14643
14797
  }
14644
14798
 
14645
14799
  // src/utils/insight-id.ts
14646
- import { createHash as createHash2 } from "node:crypto";
14800
+ import { createHash as createHash3 } from "node:crypto";
14647
14801
  function computeInsightId(userSeed, localKey) {
14648
- return createHash2("sha256").update(`${userSeed}:${localKey}`).digest("hex").slice(0, 22);
14802
+ return createHash3("sha256").update(`${userSeed}:${localKey}`).digest("hex").slice(0, 22);
14649
14803
  }
14650
14804
  function insightLocalKey(teaches, slug) {
14651
14805
  return `${teaches}:${slug}`.toLowerCase().replace(/[^a-z0-9:_-]+/g, "-").replace(/-+/g, "-");
@@ -16567,19 +16721,6 @@ async function collectTelemetryEvents(params) {
16567
16721
  const now = params.now ?? new Date().toISOString();
16568
16722
  const events = [];
16569
16723
  const adapterResults = [];
16570
- const previouslyReported = new Set(params.state.reportedAgentSlugs ?? []);
16571
- const newlyReportedAgents = [];
16572
- for (const slug of params.state.configuredAgents) {
16573
- if (!previouslyReported.has(slug)) {
16574
- events.push({
16575
- eventType: "local_agent.detected",
16576
- agentSlug: slug,
16577
- metadata: { installed: true },
16578
- timestamp: now
16579
- });
16580
- newlyReportedAgents.push(slug);
16581
- }
16582
- }
16583
16724
  for (const adapter2 of params.adapters) {
16584
16725
  let stats = "unsupported";
16585
16726
  let skills = "unsupported";
@@ -16671,11 +16812,12 @@ async function collectTelemetryEvents(params) {
16671
16812
  syncedMcpServers: params.syncedMcpServersCount,
16672
16813
  teamInstructionsApplied: params.teamInstructionsApplied,
16673
16814
  agentConfigsApplied: params.agentConfigsApplied,
16815
+ agents: [...params.state.configuredAgents],
16674
16816
  ...params.state.persona?.label ? { persona: params.state.persona.label } : {}
16675
16817
  },
16676
16818
  timestamp: now
16677
16819
  });
16678
- return { events, adapterResults, newlyReportedAgents, healthReported };
16820
+ return { events, adapterResults, healthReported };
16679
16821
  }
16680
16822
  function formatStatsLine(stats) {
16681
16823
  const parts = [];
@@ -16721,10 +16863,6 @@ function printTelemetryVerbose(result) {
16721
16863
  const errSuffix = r.error ? ` (error: ${r.error})` : "";
16722
16864
  console.log(` [${r.name}] stats: ${statsLabel}; skills: ${skillsLabel}${errSuffix}`);
16723
16865
  }
16724
- if (result.newlyReportedAgents.length > 0) {
16725
- console.log(`
16726
- First-time detections: ${result.newlyReportedAgents.join(", ")}`);
16727
- }
16728
16866
  if (result.healthReported) {
16729
16867
  console.log(" Health report: due (daily)");
16730
16868
  }
@@ -17354,12 +17492,6 @@ function buildSaveConversationSkill() {
17354
17492
  };
17355
17493
  }
17356
17494
 
17357
- // src/sync/hash.ts
17358
- import { createHash as createHash3 } from "crypto";
17359
- function contentHash(content) {
17360
- return "sha256:" + createHash3("sha256").update(content).digest("hex");
17361
- }
17362
-
17363
17495
  // src/sync/change-detect.ts
17364
17496
  function computeSyncPlan(input) {
17365
17497
  const { storedHashes, localSkills, remoteSkills } = input;
@@ -17391,6 +17523,16 @@ function computeSyncPlan(input) {
17391
17523
  const localChanged = localHash !== null && localHash !== stored.localHash;
17392
17524
  const remoteChanged = remoteHash !== stored.remoteHash;
17393
17525
  if (!local) {
17526
+ if (remote.source === "app" && !remoteChanged) {
17527
+ plan.skips.push({
17528
+ name: remote.name,
17529
+ action: "skip",
17530
+ source: remote.source,
17531
+ remoteId: remote.remoteId,
17532
+ appId: remote.appId
17533
+ });
17534
+ continue;
17535
+ }
17394
17536
  plan.pulls.push({
17395
17537
  name: remote.name,
17396
17538
  action: "pull",
@@ -17570,6 +17712,47 @@ function resolveConflictNonInteractive(prefer) {
17570
17712
  return prefer;
17571
17713
  }
17572
17714
 
17715
+ // src/sync/summary-format.ts
17716
+ function summarizeSkillPlan(plan) {
17717
+ return {
17718
+ added: plan.pulls.filter((p) => p.action === "new-remote").map((p) => p.name),
17719
+ updated: plan.pulls.filter((p) => p.action === "pull").map((p) => p.name),
17720
+ removed: plan.deletions.map((p) => p.name),
17721
+ pushed: plan.pushes.map((p) => p.name),
17722
+ unchanged: plan.skips.length,
17723
+ conflicts: plan.conflicts.length
17724
+ };
17725
+ }
17726
+ function formatNameList(names, cap2 = 6) {
17727
+ if (names.length <= cap2)
17728
+ return names.join(", ");
17729
+ return `${names.slice(0, cap2).join(", ")}, +${names.length - cap2} more`;
17730
+ }
17731
+ function formatSkillSummaryLine(c) {
17732
+ const parts = [];
17733
+ if (c.added.length)
17734
+ parts.push(`${c.added.length} added (${formatNameList(c.added)})`);
17735
+ if (c.updated.length)
17736
+ parts.push(`${c.updated.length} updated (${formatNameList(c.updated)})`);
17737
+ if (c.removed.length)
17738
+ parts.push(`${c.removed.length} removed (${formatNameList(c.removed)})`);
17739
+ if (c.pushed.length)
17740
+ parts.push(`${c.pushed.length} pushed (${formatNameList(c.pushed)})`);
17741
+ if (c.conflicts)
17742
+ parts.push(`${c.conflicts} conflict${c.conflicts === 1 ? "" : "s"}`);
17743
+ parts.push(`${c.unchanged} up to date`);
17744
+ return parts.join(", ");
17745
+ }
17746
+ var REASON_LABEL = {
17747
+ mcp: "MCP",
17748
+ settings: "settings"
17749
+ };
17750
+ function formatRestartChanges(changes) {
17751
+ if (changes.length === 0)
17752
+ return "none";
17753
+ return changes.map((c) => `${c.name} (${c.reasons.map((r) => REASON_LABEL[r]).join(", ")})`).join("; ");
17754
+ }
17755
+
17573
17756
  // src/sync/executor.ts
17574
17757
  async function executeSyncPlan(plan, resolvedConflicts, ctx) {
17575
17758
  const newHashes = {};
@@ -17585,7 +17768,7 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
17585
17768
  remoteId: action.remoteId,
17586
17769
  appId: action.appId
17587
17770
  };
17588
- console.log(` Pulled: ${action.name}`);
17771
+ vlog(` Pulled: ${action.name}`);
17589
17772
  }
17590
17773
  for (const action of plan.pushes) {
17591
17774
  if (!action.localContent || !action.remoteId)
@@ -17602,7 +17785,7 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
17602
17785
  source: "external",
17603
17786
  remoteId: action.remoteId
17604
17787
  };
17605
- console.log(` Pushed: ${action.name}`);
17788
+ vlog(` Pushed: ${action.name}`);
17606
17789
  }
17607
17790
  for (const action of plan.conflicts) {
17608
17791
  const resolution = resolvedConflicts.get(action.name);
@@ -17616,7 +17799,7 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
17616
17799
  });
17617
17800
  const hash = contentHash(action.localContent);
17618
17801
  newHashes[action.name] = { localHash: hash, remoteHash: hash, source: "external", remoteId: action.remoteId };
17619
- console.log(` Pushed (conflict resolved): ${action.name}`);
17802
+ vlog(` Pushed (conflict resolved): ${action.name}`);
17620
17803
  } else if (resolution === "remote" && action.remoteContent) {
17621
17804
  const skillFile = makeSkillFile(action.name, action.remoteContent);
17622
17805
  await writeSkillToAgents(skillFile, action.source, ctx);
@@ -17626,12 +17809,12 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
17626
17809
  source: "external",
17627
17810
  remoteId: action.remoteId
17628
17811
  };
17629
- console.log(` Pulled (conflict resolved): ${action.name}`);
17812
+ vlog(` Pulled (conflict resolved): ${action.name}`);
17630
17813
  }
17631
17814
  }
17632
17815
  for (const _action of plan.skips) {}
17633
17816
  for (const action of plan.deletions) {
17634
- console.log(` Deleted locally: ${action.name}`);
17817
+ vlog(` Deleted locally: ${action.name}`);
17635
17818
  }
17636
17819
  return newHashes;
17637
17820
  }
@@ -17661,6 +17844,53 @@ function extractDescription(content) {
17661
17844
  return match ? match[1].trim() : "";
17662
17845
  }
17663
17846
 
17847
+ // src/sync/restart-stamp.ts
17848
+ function computeRestartReasons(input) {
17849
+ const settingsSet = new Set(input.settingsChangedSlugs);
17850
+ const out = {};
17851
+ for (const agent of input.agents) {
17852
+ if (agent.connectOnly)
17853
+ continue;
17854
+ const reasons = [];
17855
+ if ((input.mcpChanged || agent.isNew) && agent.receivesMcp)
17856
+ reasons.push("mcp");
17857
+ if (settingsSet.has(agent.slug))
17858
+ reasons.push("settings");
17859
+ if (reasons.length > 0)
17860
+ out[agent.slug] = reasons;
17861
+ }
17862
+ return out;
17863
+ }
17864
+ function summarizeRealSkillChanges(newLocalHashes, priorLocalHashes, materializedDeletions) {
17865
+ const added = [];
17866
+ const updated = [];
17867
+ for (const [name, hash] of Object.entries(newLocalHashes)) {
17868
+ if (!(name in priorLocalHashes))
17869
+ added.push(name);
17870
+ else if (priorLocalHashes[name] !== hash)
17871
+ updated.push(name);
17872
+ }
17873
+ return {
17874
+ added: added.sort(),
17875
+ updated: updated.sort(),
17876
+ removed: [...materializedDeletions].sort()
17877
+ };
17878
+ }
17879
+ function hasRealSkillChange(c) {
17880
+ return c.added.length > 0 || c.updated.length > 0 || c.removed.length > 0;
17881
+ }
17882
+ function sameStringSet(a, b) {
17883
+ const setA = new Set(a ?? []);
17884
+ const setB = new Set(b ?? []);
17885
+ if (setA.size !== setB.size)
17886
+ return false;
17887
+ for (const item of setA) {
17888
+ if (!setB.has(item))
17889
+ return false;
17890
+ }
17891
+ return true;
17892
+ }
17893
+
17664
17894
  // src/commands/sync.ts
17665
17895
  async function printAdoptionHint(credentials, workspaceId) {
17666
17896
  if (!workspaceId)
@@ -17745,6 +17975,7 @@ async function refreshConfiguredAgents(state) {
17745
17975
  }
17746
17976
  async function syncFromState(state, statePath2, credentials, opts) {
17747
17977
  const client = new ApiClient(credentials);
17978
+ setVerbose(!!opts.verbose);
17748
17979
  if (!state.workspaceName || state.workspaceName === state.workspaceId || !state.workspaceSlug) {
17749
17980
  try {
17750
17981
  const workspaces = await client.listWorkspaces();
@@ -17756,11 +17987,12 @@ async function syncFromState(state, statePath2, credentials, opts) {
17756
17987
  } catch {}
17757
17988
  }
17758
17989
  console.log(`Syncing workspace: ${state.workspaceName || state.workspaceId}`);
17990
+ let addedThisSync = [];
17759
17991
  if (shouldRedetect(state, opts)) {
17760
17992
  console.log(" Detecting installed agents...");
17761
- const added = await refreshConfiguredAgents(state);
17762
- if (added.length > 0) {
17763
- console.log(` Detected new agent${added.length > 1 ? "s" : ""}: ${added.join(", ")}`);
17993
+ addedThisSync = await refreshConfiguredAgents(state);
17994
+ if (addedThisSync.length > 0) {
17995
+ console.log(` Detected new agent${addedThisSync.length > 1 ? "s" : ""}: ${addedThisSync.join(", ")}`);
17764
17996
  }
17765
17997
  }
17766
17998
  console.log(" Fetching workspace data...");
@@ -17802,6 +18034,8 @@ async function syncFromState(state, statePath2, credentials, opts) {
17802
18034
  headers: { Authorization: `Bearer ${credentials.apiKey}` },
17803
18035
  description: "Runwork workspace tools: query entities, trigger workflows, manage integrations, run agents, and access shared data across your team's apps."
17804
18036
  });
18037
+ const mcpEntriesHash = configHash([...mcpEntries].sort((a, b) => a.name.localeCompare(b.name)));
18038
+ const mcpChanged = state.lastMcpEntriesHash !== undefined && state.lastMcpEntriesHash !== mcpEntriesHash;
17805
18039
  const remoteSkills = [];
17806
18040
  for (const s of externalSkills) {
17807
18041
  remoteSkills.push({ name: s.name, content: s.content, source: "external", remoteId: s.id });
@@ -17831,12 +18065,9 @@ async function syncFromState(state, statePath2, credentials, opts) {
17831
18065
  plan.skips.push(...plan.conflicts.map((c) => ({ ...c, action: "skip" })));
17832
18066
  plan.conflicts = [];
17833
18067
  }
17834
- const hasChanges = plan.pulls.length > 0 || plan.pushes.length > 0 || plan.conflicts.length > 0 || plan.deletions.length > 0;
17835
- if (hasChanges) {
18068
+ console.log(` Skills: ${formatSkillSummaryLine(summarizeSkillPlan(plan))}`);
18069
+ if (isVerbose())
17836
18070
  printSyncSummary(plan);
17837
- } else {
17838
- console.log(" All skills up to date.");
17839
- }
17840
18071
  const adapters = [];
17841
18072
  for (const slug of state.configuredAgents) {
17842
18073
  const adapter2 = getAdapterBySlug(slug);
@@ -17876,6 +18107,7 @@ async function syncFromState(state, statePath2, credentials, opts) {
17876
18107
  }
17877
18108
  }
17878
18109
  }
18110
+ const settingsChangedSlugs = [];
17879
18111
  const scopes = state.scope === "both" ? ["project", "user"] : [state.scope];
17880
18112
  if (adapters.length > 0) {
17881
18113
  console.log(` Syncing to: ${adapters.map((a) => a.name).join(", ")}`);
@@ -17924,6 +18156,8 @@ async function syncFromState(state, statePath2, credentials, opts) {
17924
18156
  buildSaveConversationSkill()
17925
18157
  ];
17926
18158
  const builtInNames = builtInSkills.map((s) => s.name);
18159
+ const builtInSkillsHash = configHash(builtInSkills.map((s) => s.content));
18160
+ const builtInSkillsChanged = state.builtInSkillsHash !== undefined && state.builtInSkillsHash !== builtInSkillsHash;
17927
18161
  const summary = {
17928
18162
  adaptersProcessed: 0,
17929
18163
  adaptersFailed: 0,
@@ -17933,16 +18167,18 @@ async function syncFromState(state, statePath2, credentials, opts) {
17933
18167
  instructionHintWrites: 0,
17934
18168
  hookInstallCalls: 0
17935
18169
  };
18170
+ const mcpFailedAdapters = new Set;
18171
+ const skillFailedAdapters = new Set;
17936
18172
  for (const adapter2 of adapters) {
17937
18173
  if (isConnectOnlyAgent(getRegistryAgent(adapter2.slug))) {
17938
- console.log(` [${adapter2.name}] Connect-only agent: no local files to sync`);
18174
+ vlog(` [${adapter2.name}] Connect-only agent: no local files to sync`);
17939
18175
  summary.adaptersProcessed++;
17940
18176
  continue;
17941
18177
  }
17942
18178
  let adapterFailedAnyScope = false;
17943
18179
  for (const scope of scopes) {
17944
- try {
17945
- if (adapter2.supportsSkills()) {
18180
+ if (adapter2.supportsSkills()) {
18181
+ try {
17946
18182
  const skipAppSkills = adapter2.mcpProvidesSkills && mcpEntries.length > 0;
17947
18183
  const scopeSkills = remoteSkills.filter((s) => {
17948
18184
  if (builtInNameSet.has(s.name))
@@ -17962,25 +18198,41 @@ async function syncFromState(state, statePath2, credentials, opts) {
17962
18198
  description: s.source === "app" ? buildAppSkillDescription(s.name, registries) || `${s.name} - Runwork workspace application` : `${s.name} - Runwork workspace skill`
17963
18199
  }));
17964
18200
  const allSkillFiles = [...builtInSkills, ...workspaceSkillFiles];
17965
- await adapter2.writeSkills(allSkillFiles, scope);
17966
- summary.skillWrites++;
17967
- summary.skillFilesWritten += allSkillFiles.length;
17968
- console.log(` [${adapter2.name}] Wrote ${allSkillFiles.length} skills (${scope}): ` + `${builtInSkills.length} built-in [${builtInNames.join(", ")}], ` + `${workspaceSkillFiles.length} workspace`);
17969
- } else {
17970
- console.log(` [${adapter2.name}] Skipped skills (${scope}): adapter does not support skills`);
18201
+ const written = await adapter2.writeSkills(allSkillFiles, scope);
18202
+ if (written > 0) {
18203
+ summary.skillWrites++;
18204
+ summary.skillFilesWritten += written;
18205
+ vlog(` [${adapter2.name}] Wrote ${written} skills (${scope}): ` + `${builtInSkills.length} built-in [${builtInNames.join(", ")}], ` + `${workspaceSkillFiles.length} workspace`);
18206
+ } else {
18207
+ vlog(` [${adapter2.name}] Skipped skills (${scope}): adapter writes none at this scope`);
18208
+ }
18209
+ } catch (err) {
18210
+ adapterFailedAnyScope = true;
18211
+ skillFailedAdapters.add(adapter2.slug);
18212
+ console.warn(` [${adapter2.name}] Failed skills (${scope}): ${err instanceof Error ? err.message : err}`);
17971
18213
  }
17972
- if (adapter2.supportsMcpScope(scope) && mcpEntries.length > 0) {
18214
+ } else {
18215
+ vlog(` [${adapter2.name}] Skipped skills (${scope}): adapter does not support skills`);
18216
+ }
18217
+ if (adapter2.supportsMcpScope(scope) && mcpEntries.length > 0) {
18218
+ try {
17973
18219
  await adapter2.writeMcpServers(mcpEntries, scope);
17974
18220
  summary.mcpServerWrites++;
17975
- console.log(` [${adapter2.name}] Updated ${mcpEntries.length} MCP server${mcpEntries.length > 1 ? "s" : ""} (${scope})`);
17976
- } else if (mcpEntries.length === 0) {
17977
- console.log(` [${adapter2.name}] Skipped MCP servers (${scope}): no workspace MCP servers configured`);
17978
- } else if (!adapter2.supportsMcpScope(scope)) {
17979
- console.log(` [${adapter2.name}] Skipped MCP servers (${scope}): adapter does not support MCP at this scope`);
18221
+ vlog(` [${adapter2.name}] Updated ${mcpEntries.length} MCP server${mcpEntries.length > 1 ? "s" : ""} (${scope})`);
18222
+ } catch (err) {
18223
+ adapterFailedAnyScope = true;
18224
+ mcpFailedAdapters.add(adapter2.slug);
18225
+ console.warn(` [${adapter2.name}] Failed MCP servers (${scope}): ${err instanceof Error ? err.message : err}`);
17980
18226
  }
18227
+ } else if (mcpEntries.length === 0) {
18228
+ vlog(` [${adapter2.name}] Skipped MCP servers (${scope}): no workspace MCP servers configured`);
18229
+ } else if (!adapter2.supportsMcpScope(scope)) {
18230
+ vlog(` [${adapter2.name}] Skipped MCP servers (${scope}): adapter does not support MCP at this scope`);
18231
+ }
18232
+ try {
17981
18233
  await adapter2.writeInstructionHint(instructionHint, scope);
17982
18234
  summary.instructionHintWrites++;
17983
- console.log(` [${adapter2.name}] Updated instruction hints (${scope})`);
18235
+ vlog(` [${adapter2.name}] Updated instruction hints (${scope})`);
17984
18236
  if (adapter2.writeBuiltInHooks) {
17985
18237
  await adapter2.writeBuiltInHooks(scope);
17986
18238
  summary.hookInstallCalls++;
@@ -18019,7 +18271,7 @@ async function syncFromState(state, statePath2, credentials, opts) {
18019
18271
  try {
18020
18272
  await adapter2.writeTeamInstructions(fullInstructions, scope);
18021
18273
  teamInstructionsApplied = true;
18022
- console.log(` [${adapter2.name}] Updated team instructions (${scope})`);
18274
+ vlog(` [${adapter2.name}] Updated team instructions (${scope})`);
18023
18275
  } catch {}
18024
18276
  }
18025
18277
  }
@@ -18040,7 +18292,7 @@ async function syncFromState(state, statePath2, credentials, opts) {
18040
18292
  await adapter2.writeAgentConfig(configWithoutInstructions, scope);
18041
18293
  agentConfigsApplied++;
18042
18294
  const configKeys = Object.keys(configWithoutInstructions).join(", ");
18043
- console.log(` [${adapter2.name}] Updated agent config: ${configKeys} (${scope})`);
18295
+ vlog(` [${adapter2.name}] Updated agent config: ${configKeys} (${scope})`);
18044
18296
  } catch {}
18045
18297
  }
18046
18298
  }
@@ -18095,6 +18347,12 @@ async function syncFromState(state, statePath2, credentials, opts) {
18095
18347
  try {
18096
18348
  const baseline = agentState?.lastInjected;
18097
18349
  await adapter2.writeAgentConfig(mergedConfig, "user", baseline);
18350
+ if (baked && !resolved.bootstrapped) {
18351
+ const prev = agentState?.lastInjected;
18352
+ if (!sameStringSet(prev?.allow, resolved.applicableAllow) || !sameStringSet(prev?.deny, resolved.applicableDeny)) {
18353
+ settingsChangedSlugs.push(slug);
18354
+ }
18355
+ }
18098
18356
  if (baked) {
18099
18357
  const nextState = {
18100
18358
  lastInjected: {
@@ -18108,11 +18366,11 @@ async function syncFromState(state, statePath2, credentials, opts) {
18108
18366
  };
18109
18367
  state.agentDefaults[slug] = nextState;
18110
18368
  if (resolved.bootstrapped) {
18111
- console.log(` [${adapter2.name}] Bootstrapped default-rule tracking (defaults apply on next sync)`);
18369
+ vlog(` [${adapter2.name}] Bootstrapped default-rule tracking (defaults apply on next sync)`);
18112
18370
  } else if (resolved.applicableAllow.length || resolved.applicableDeny.length || resolved.removalsThisSync.allow || resolved.removalsThisSync.deny) {
18113
18371
  const totalRemovals = resolved.removalsThisSync.allow + resolved.removalsThisSync.deny;
18114
18372
  const removalNote = totalRemovals > 0 ? ` (${totalRemovals} new opt-out${totalRemovals > 1 ? "s" : ""} honored)` : "";
18115
- console.log(` [${adapter2.name}] Applied defaults: ${resolved.applicableAllow.length} allow, ${resolved.applicableDeny.length} deny${removalNote}`);
18373
+ vlog(` [${adapter2.name}] Applied defaults: ${resolved.applicableAllow.length} allow, ${resolved.applicableDeny.length} deny${removalNote}`);
18116
18374
  }
18117
18375
  }
18118
18376
  if (team)
@@ -18126,11 +18384,12 @@ async function syncFromState(state, statePath2, credentials, opts) {
18126
18384
  const runworkDir = join35(homedir17(), ".runwork");
18127
18385
  const result = adapter2.registerDesktopWorkspace(runworkDir, "Runwork");
18128
18386
  if (result === "written") {
18129
- console.log(` [${adapter2.name}] Registered workspace in Codex desktop app`);
18387
+ vlog(` [${adapter2.name}] Registered workspace in Codex desktop app`);
18130
18388
  }
18131
18389
  break;
18132
18390
  }
18133
18391
  }
18392
+ const prevMcpNames = state.mcpServers ?? [];
18134
18393
  state.lastSyncAt = new Date().toISOString();
18135
18394
  state.mcpServers = mcpEntries.map((e) => e.name);
18136
18395
  state.skills = remoteSkills.map((s) => s.name);
@@ -18138,6 +18397,57 @@ async function syncFromState(state, statePath2, credentials, opts) {
18138
18397
  "runwork",
18139
18398
  ...remoteSkills.map((s) => s.name.toLowerCase().replace(/[^a-z0-9]+/g, "-"))
18140
18399
  ];
18400
+ const priorSkillHashes = state.skillHashes || {};
18401
+ const newLocalHashes = {};
18402
+ for (const [name, h] of Object.entries(newHashes))
18403
+ newLocalHashes[name] = h.localHash;
18404
+ const priorLocalHashes = {};
18405
+ for (const [name, h] of Object.entries(priorSkillHashes))
18406
+ priorLocalHashes[name] = h.localHash;
18407
+ const materializedDeletions = plan.deletions.filter((d) => d.localContent !== undefined).map((d) => d.name);
18408
+ const skillChanges = summarizeRealSkillChanges(newLocalHashes, priorLocalHashes, materializedDeletions);
18409
+ const newMcpNames = mcpEntries.map((e) => e.name);
18410
+ const prevMcpSet = new Set(prevMcpNames);
18411
+ const newMcpSet = new Set(newMcpNames);
18412
+ const mcpAdded = mcpChanged ? newMcpNames.filter((n) => !prevMcpSet.has(n)) : [];
18413
+ const mcpRemoved = mcpChanged ? prevMcpNames.filter((n) => !newMcpSet.has(n)) : [];
18414
+ const addedSet = new Set(addedThisSync);
18415
+ const restartReasons = computeRestartReasons({
18416
+ agents: adapters.map((a) => ({
18417
+ slug: a.slug,
18418
+ receivesMcp: mcpEntries.length > 0 && scopes.some((sc) => a.supportsMcpScope(sc)) && !mcpFailedAdapters.has(a.slug),
18419
+ connectOnly: isConnectOnlyAgent(getRegistryAgent(a.slug)),
18420
+ isNew: !!opts.initialSetup || addedSet.has(a.slug)
18421
+ })),
18422
+ mcpChanged,
18423
+ settingsChangedSlugs
18424
+ });
18425
+ if (Object.keys(restartReasons).length > 0) {
18426
+ if (!state.agentConfigChangedAt)
18427
+ state.agentConfigChangedAt = {};
18428
+ for (const [slug, reasons] of Object.entries(restartReasons)) {
18429
+ state.agentConfigChangedAt[slug] = { changedAt: state.lastSyncAt, reasons };
18430
+ }
18431
+ }
18432
+ if (hasRealSkillChange(skillChanges) || builtInSkillsChanged || mcpChanged || settingsChangedSlugs.length > 0) {
18433
+ state.lastSyncChange = {
18434
+ at: state.lastSyncAt,
18435
+ skills: skillChanges,
18436
+ builtInSkillsChanged,
18437
+ mcp: { changed: mcpChanged, added: mcpAdded, removed: mcpRemoved },
18438
+ settingsSlugs: settingsChangedSlugs
18439
+ };
18440
+ }
18441
+ if (mcpFailedAdapters.size === 0)
18442
+ state.lastMcpEntriesHash = mcpEntriesHash;
18443
+ if (skillFailedAdapters.size === 0)
18444
+ state.builtInSkillsHash = builtInSkillsHash;
18445
+ const nameBySlug = new Map(adapters.map((a) => [a.slug, a.name]));
18446
+ const restartChanges = Object.entries(restartReasons).map(([slug, reasons]) => ({
18447
+ name: nameBySlug.get(slug) ?? slug,
18448
+ reasons
18449
+ }));
18450
+ console.log(` Restart-worthy changes: ${formatRestartChanges(restartChanges)}`);
18141
18451
  const mergedHashes = { ...state.skillHashes || {} };
18142
18452
  for (const [name, hash] of Object.entries(newHashes)) {
18143
18453
  mergedHashes[name] = hash;
@@ -18159,25 +18469,29 @@ async function syncFromState(state, statePath2, credentials, opts) {
18159
18469
  if (opts.verbose)
18160
18470
  printTelemetryVerbose(telemetry);
18161
18471
  await client.reportTelemetry(state.workspaceId, telemetry.events);
18162
- if (telemetry.healthReported)
18472
+ if (telemetry.healthReported) {
18163
18473
  state.lastHealthReportAt = new Date().toISOString();
18164
- state.reportedAgentSlugs = [
18165
- ...new Set([...state.reportedAgentSlugs ?? [], ...telemetry.newlyReportedAgents])
18166
- ];
18167
- writeFileSync29(statePath2, JSON.stringify(state, null, 2));
18474
+ writeFileSync29(statePath2, JSON.stringify(state, null, 2));
18475
+ }
18168
18476
  } catch {}
18169
- const summaryParts = [];
18170
- summaryParts.push(`${summary.adaptersProcessed} adapter${summary.adaptersProcessed === 1 ? "" : "s"}`);
18171
- if (summary.adaptersFailed > 0)
18172
- summaryParts.push(`${summary.adaptersFailed} failed`);
18173
- summaryParts.push(`${summary.skillWrites} skill write${summary.skillWrites === 1 ? "" : "s"} (${summary.skillFilesWritten} files)`);
18174
- if (summary.mcpServerWrites > 0)
18175
- summaryParts.push(`${summary.mcpServerWrites} MCP config write${summary.mcpServerWrites === 1 ? "" : "s"}`);
18176
- if (summary.hookInstallCalls > 0)
18177
- summaryParts.push(`${summary.hookInstallCalls} hook install${summary.hookInstallCalls === 1 ? "" : "s"}`);
18178
- summaryParts.push(`${summary.instructionHintWrites} instruction hint write${summary.instructionHintWrites === 1 ? "" : "s"}`);
18179
- console.log(`
18477
+ const failedNote = summary.adaptersFailed > 0 ? ` (${summary.adaptersFailed} failed)` : "";
18478
+ if (isVerbose()) {
18479
+ const summaryParts = [];
18480
+ summaryParts.push(`${summary.adaptersProcessed} adapter${summary.adaptersProcessed === 1 ? "" : "s"}`);
18481
+ if (summary.adaptersFailed > 0)
18482
+ summaryParts.push(`${summary.adaptersFailed} failed`);
18483
+ summaryParts.push(`${summary.skillWrites} skill write${summary.skillWrites === 1 ? "" : "s"} (${summary.skillFilesWritten} files)`);
18484
+ if (summary.mcpServerWrites > 0)
18485
+ summaryParts.push(`${summary.mcpServerWrites} MCP config write${summary.mcpServerWrites === 1 ? "" : "s"}`);
18486
+ if (summary.hookInstallCalls > 0)
18487
+ summaryParts.push(`${summary.hookInstallCalls} hook install${summary.hookInstallCalls === 1 ? "" : "s"}`);
18488
+ summaryParts.push(`${summary.instructionHintWrites} instruction hint write${summary.instructionHintWrites === 1 ? "" : "s"}`);
18489
+ console.log(`
18180
18490
  Summary: ${summaryParts.join(", ")}.`);
18491
+ } else {
18492
+ console.log(`
18493
+ ${summary.adaptersProcessed} agent${summary.adaptersProcessed === 1 ? "" : "s"} synced${failedNote}.`);
18494
+ }
18181
18495
  try {
18182
18496
  const cadence = loadCadenceState();
18183
18497
  const lastMs = cadence.lastReflectedAt ? new Date(cadence.lastReflectedAt).getTime() : 0;
@@ -18370,7 +18684,8 @@ Syncing workspace data...
18370
18684
  dryRun: false,
18371
18685
  pullOnly: true,
18372
18686
  yes: true,
18373
- prefer: "remote"
18687
+ prefer: "remote",
18688
+ initialSetup: true
18374
18689
  });
18375
18690
  }
18376
18691
  console.log("\nSetup complete. Run `runwork sync` anytime to refresh.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runwork",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "CLI for Runwork: develop, preview, and deploy Runwork apps from your local machine.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Runwork <info@runwork.ai> (https://www.runwork.ai)",