runwork 0.10.4 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/__tests__/claude-code-managed-block.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-code-managed-block.test.js +97 -0
- package/dist/agents/__tests__/codex-minimum-permissions.test.d.ts +1 -0
- package/dist/agents/__tests__/codex-minimum-permissions.test.js +82 -0
- package/dist/agents/__tests__/cursor-merge.test.d.ts +1 -0
- package/dist/agents/__tests__/cursor-merge.test.js +66 -0
- package/dist/agents/__tests__/defaults-merge.test.d.ts +1 -0
- package/dist/agents/__tests__/defaults-merge.test.js +268 -0
- package/dist/agents/__tests__/intro-skill.test.js +32 -0
- package/dist/agents/claude-code.d.ts +5 -0
- package/dist/agents/claude-code.js +29 -0
- package/dist/agents/cursor.d.ts +23 -1
- package/dist/agents/cursor.js +42 -3
- package/dist/agents/default-config.d.ts +30 -0
- package/dist/agents/default-config.js +67 -0
- package/dist/agents/defaults-merge.d.ts +72 -0
- package/dist/agents/defaults-merge.js +131 -0
- package/dist/agents/intro-skill.d.ts +9 -0
- package/dist/agents/intro-skill.js +41 -0
- package/dist/agents/types.d.ts +31 -2
- package/dist/commands/__tests__/setup-persona.test.d.ts +1 -0
- package/dist/commands/__tests__/setup-persona.test.js +31 -0
- package/dist/commands/info.d.ts +1 -1
- package/dist/commands/info.js +7 -2
- package/dist/commands/setup.d.ts +7 -0
- package/dist/commands/setup.js +22 -0
- package/dist/commands/sync.js +147 -59
- package/dist/generated/bundled-types.js +33 -33
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/types.d.ts +36 -0
- package/dist/ui/banner.js +1 -1
- package/dist/utils/app-info.d.ts +4 -0
- package/dist/utils/app-info.js +17 -0
- package/package.json +1 -1
package/dist/commands/sync.js
CHANGED
|
@@ -7,6 +7,8 @@ import { ApiClient } from '../api/client.js';
|
|
|
7
7
|
import { getAdapterBySlug, detectAgents } from '../agents/detect.js';
|
|
8
8
|
import { CodexAdapter } from '../agents/codex.js';
|
|
9
9
|
import { RUNWORK_MCP_PREFIX, RUNWORK_WORKSPACE_MCP_NAME } from '../agents/types.js';
|
|
10
|
+
import { RUNWORK_AGENT_DEFAULTS, AGENT_DEFAULTS_SCHEMA_VERSION } from '../agents/default-config.js';
|
|
11
|
+
import { resolveAgentDefaults } from '../agents/defaults-merge.js';
|
|
10
12
|
import { collectTelemetryEvents, printTelemetryVerbose, summarizeTelemetryForDryRun, } from './sync-telemetry.js';
|
|
11
13
|
import { generateIntroSkill, generateInstructionHint, buildAppSkillDescription } from '../agents/intro-skill.js';
|
|
12
14
|
import { computeSyncPlan } from '../sync/change-detect.js';
|
|
@@ -291,6 +293,7 @@ export async function syncFromState(state, statePath, credentials, opts) {
|
|
|
291
293
|
appCount: allSkills.filter(s => s.type === 'app').length,
|
|
292
294
|
skillCount: remoteSkills.length,
|
|
293
295
|
mcpServerCount: mcpEntries.length,
|
|
296
|
+
persona: state.persona,
|
|
294
297
|
});
|
|
295
298
|
// For project scope: determine which app skill to include (only current app's skill)
|
|
296
299
|
let projectAppSkillFilter = null;
|
|
@@ -346,85 +349,170 @@ export async function syncFromState(state, statePath, credentials, opts) {
|
|
|
346
349
|
}
|
|
347
350
|
}
|
|
348
351
|
}
|
|
349
|
-
// Pull
|
|
352
|
+
// Pull team config from server. A failure here must not stop the
|
|
353
|
+
// unified user-scope write below, which applies network and minimum-permission
|
|
354
|
+
// floors regardless of whether team config was fetched.
|
|
350
355
|
let teamInstructionsApplied = false;
|
|
351
356
|
let agentConfigsApplied = 0;
|
|
357
|
+
let teamInstructions;
|
|
358
|
+
let agentConfigs;
|
|
352
359
|
try {
|
|
353
360
|
const onboardingConfig = await client.getOnboardingConfig(state.workspaceId);
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
361
|
+
teamInstructions = onboardingConfig?.config?.teamInstructions ?? undefined;
|
|
362
|
+
agentConfigs = onboardingConfig?.config?.agentConfigs ?? undefined;
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
// Team config pull is non-fatal — proceed with no team-managed overrides.
|
|
366
|
+
}
|
|
367
|
+
// Write team instructions and/or per-agent extra instructions.
|
|
368
|
+
for (const adapter of adapters) {
|
|
369
|
+
if (!adapter.writeTeamInstructions)
|
|
370
|
+
continue;
|
|
371
|
+
const agentExtra = agentConfigs?.[adapter.slug]?.extraInstructions;
|
|
372
|
+
const parts = [teamInstructions, agentExtra].filter(Boolean);
|
|
373
|
+
if (parts.length === 0)
|
|
374
|
+
continue;
|
|
375
|
+
const fullInstructions = parts.join('\n\n');
|
|
376
|
+
for (const scope of scopes) {
|
|
377
|
+
try {
|
|
378
|
+
await adapter.writeTeamInstructions(fullInstructions, scope);
|
|
379
|
+
teamInstructionsApplied = true;
|
|
380
|
+
console.log(` [${adapter.name}] Updated team instructions (${scope})`);
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
// Best-effort per adapter/scope
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
// Apply per-agent project-scope config (team overrides only).
|
|
388
|
+
// User scope is handled below in the unified defaults+network+minimum block.
|
|
389
|
+
if (agentConfigs) {
|
|
358
390
|
for (const adapter of adapters) {
|
|
359
|
-
|
|
391
|
+
const agentConfig = agentConfigs[adapter.slug];
|
|
392
|
+
if (!agentConfig || !adapter.writeAgentConfig)
|
|
360
393
|
continue;
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
if (parts.length === 0)
|
|
394
|
+
const { extraInstructions: _, ...configWithoutInstructions } = agentConfig;
|
|
395
|
+
if (Object.keys(configWithoutInstructions).length === 0)
|
|
364
396
|
continue;
|
|
365
|
-
const fullInstructions = parts.join('\n\n');
|
|
366
397
|
for (const scope of scopes) {
|
|
398
|
+
if (scope === 'user')
|
|
399
|
+
continue; // handled in unified block below
|
|
367
400
|
try {
|
|
368
|
-
await adapter.
|
|
369
|
-
|
|
370
|
-
|
|
401
|
+
await adapter.writeAgentConfig(configWithoutInstructions, scope);
|
|
402
|
+
agentConfigsApplied++;
|
|
403
|
+
const configKeys = Object.keys(configWithoutInstructions).join(', ');
|
|
404
|
+
console.log(` [${adapter.name}] Updated agent config: ${configKeys} (${scope})`);
|
|
371
405
|
}
|
|
372
406
|
catch {
|
|
373
407
|
// Best-effort per adapter/scope
|
|
374
408
|
}
|
|
375
409
|
}
|
|
376
410
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
411
|
+
}
|
|
412
|
+
// Unified user-scope write: merge baked defaults with team config, network
|
|
413
|
+
// allowlist, and minimum permissions into a single writeAgentConfig call
|
|
414
|
+
// per adapter. The merge layer tracks injected defaults in state.agentDefaults
|
|
415
|
+
// so user-removed entries become sticky opt-outs.
|
|
416
|
+
if (scopes.includes('user')) {
|
|
417
|
+
const networkDomains = ['runwork.ai', '*.runwork.ai'];
|
|
418
|
+
try {
|
|
419
|
+
const baseHost = new URL(baseUrl).hostname;
|
|
420
|
+
if (baseHost !== 'runwork.ai' && !baseHost.endsWith('.runwork.ai')) {
|
|
421
|
+
networkDomains.push(baseHost, `*.${baseHost}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
catch { /* use defaults */ }
|
|
425
|
+
// Order arrays list each agent's valid values from most restrictive to most
|
|
426
|
+
// permissive. The floor logic upgrades anything strictly below `minimum`,
|
|
427
|
+
// including unknown values (-1 < minimumIdx). That makes it critical to
|
|
428
|
+
// include the agent's most-permissive option in `order` — otherwise a user
|
|
429
|
+
// who explicitly set, say, `sandbox_mode = "danger-full-access"` gets
|
|
430
|
+
// silently downgraded to `workspace-write`.
|
|
431
|
+
const minimumPermissions = [
|
|
432
|
+
{ field: 'approval_policy', order: ['untrusted', 'on-request', 'on-failure', 'never'], minimum: 'on-request' },
|
|
433
|
+
{ field: 'sandbox_mode', order: ['read-only', 'workspace-write', 'danger-full-access'], minimum: 'workspace-write' },
|
|
434
|
+
];
|
|
435
|
+
// agentDefaults lifecycle: full `runwork uninstall` deletes ~/.runwork
|
|
436
|
+
// (state and all per-agent defaults state with it). Re-running `runwork
|
|
437
|
+
// setup` overwrites setup.json without agentDefaults so every remaining
|
|
438
|
+
// tool bootstraps fresh on the next sync. refreshConfiguredAgents only
|
|
439
|
+
// adds slugs (never removes), so we don't need orphan-entry pruning here.
|
|
440
|
+
if (!state.agentDefaults)
|
|
441
|
+
state.agentDefaults = {};
|
|
442
|
+
for (const adapter of adapters) {
|
|
443
|
+
if (!adapter.writeAgentConfig)
|
|
444
|
+
continue;
|
|
445
|
+
const slug = adapter.slug;
|
|
446
|
+
const baked = RUNWORK_AGENT_DEFAULTS[slug];
|
|
447
|
+
const agentState = state.agentDefaults[slug];
|
|
448
|
+
const team = agentConfigs?.[slug];
|
|
449
|
+
// onDisk = undefined signals "removal detection is not possible" — used for
|
|
450
|
+
// markerless adapters (Cursor) and for any adapter without readManagedBlock.
|
|
451
|
+
// The resolver treats undefined differently from an empty array: it skips
|
|
452
|
+
// opt-out fabrication entirely and trusts the baseline mechanism in the
|
|
453
|
+
// adapter to preserve user edits via subtraction.
|
|
454
|
+
let onDisk;
|
|
455
|
+
if (baked && adapter.readManagedBlock) {
|
|
456
|
+
try {
|
|
457
|
+
onDisk = await adapter.readManagedBlock('user');
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
onDisk = undefined;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
const resolved = resolveAgentDefaults({
|
|
464
|
+
baked,
|
|
465
|
+
state: agentState,
|
|
466
|
+
onDisk,
|
|
467
|
+
team: team?.permissionRules,
|
|
468
|
+
});
|
|
469
|
+
const mergedConfig = {
|
|
470
|
+
modelPreference: team?.modelPreference,
|
|
471
|
+
permissionRules: (resolved.managedAllow.length || resolved.managedDeny.length || team?.permissionRules?.defaultMode)
|
|
472
|
+
? {
|
|
473
|
+
...(resolved.managedAllow.length ? { allow: resolved.managedAllow } : {}),
|
|
474
|
+
...(resolved.managedDeny.length ? { deny: resolved.managedDeny } : {}),
|
|
475
|
+
...(team?.permissionRules?.defaultMode ? { defaultMode: team.permissionRules.defaultMode } : {}),
|
|
393
476
|
}
|
|
394
|
-
|
|
395
|
-
|
|
477
|
+
: undefined,
|
|
478
|
+
networkAllowlist: networkDomains,
|
|
479
|
+
minimumPermissions,
|
|
480
|
+
};
|
|
481
|
+
try {
|
|
482
|
+
const baseline = agentState?.lastInjected;
|
|
483
|
+
await adapter.writeAgentConfig(mergedConfig, 'user', baseline);
|
|
484
|
+
if (baked) {
|
|
485
|
+
const nextState = {
|
|
486
|
+
lastInjected: {
|
|
487
|
+
allow: resolved.applicableAllow,
|
|
488
|
+
deny: resolved.applicableDeny,
|
|
489
|
+
},
|
|
490
|
+
userOptOuts: {
|
|
491
|
+
allow: resolved.newOptOutsAllow,
|
|
492
|
+
deny: resolved.newOptOutsDeny,
|
|
493
|
+
},
|
|
494
|
+
};
|
|
495
|
+
state.agentDefaults[slug] = nextState;
|
|
496
|
+
// Diagnostic log line (per "invisible / sync log only" policy)
|
|
497
|
+
if (resolved.bootstrapped) {
|
|
498
|
+
console.log(` [${adapter.name}] Bootstrapped default-rule tracking (defaults apply on next sync)`);
|
|
499
|
+
}
|
|
500
|
+
else if (resolved.applicableAllow.length || resolved.applicableDeny.length || resolved.removalsThisSync.allow || resolved.removalsThisSync.deny) {
|
|
501
|
+
const totalRemovals = resolved.removalsThisSync.allow + resolved.removalsThisSync.deny;
|
|
502
|
+
const removalNote = totalRemovals > 0
|
|
503
|
+
? ` (${totalRemovals} new opt-out${totalRemovals > 1 ? 's' : ''} honored)`
|
|
504
|
+
: '';
|
|
505
|
+
console.log(` [${adapter.name}] Applied defaults: ${resolved.applicableAllow.length} allow, ${resolved.applicableDeny.length} deny${removalNote}`);
|
|
396
506
|
}
|
|
397
507
|
}
|
|
508
|
+
if (team)
|
|
509
|
+
agentConfigsApplied++;
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
// Best-effort per adapter
|
|
398
513
|
}
|
|
399
514
|
}
|
|
400
|
-
|
|
401
|
-
catch {
|
|
402
|
-
// Team config pull is non-fatal
|
|
403
|
-
}
|
|
404
|
-
// Ensure sandbox network access for agents that run in sandboxed environments (e.g. Cursor)
|
|
405
|
-
const networkDomains = ['runwork.ai', '*.runwork.ai'];
|
|
406
|
-
try {
|
|
407
|
-
const baseHost = new URL(baseUrl).hostname;
|
|
408
|
-
if (baseHost !== 'runwork.ai' && !baseHost.endsWith('.runwork.ai')) {
|
|
409
|
-
networkDomains.push(baseHost, `*.${baseHost}`);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
catch { /* use defaults */ }
|
|
413
|
-
for (const adapter of adapters) {
|
|
414
|
-
if (!adapter.writeAgentConfig)
|
|
415
|
-
continue;
|
|
416
|
-
try {
|
|
417
|
-
await adapter.writeAgentConfig({
|
|
418
|
-
networkAllowlist: networkDomains,
|
|
419
|
-
minimumPermissions: [
|
|
420
|
-
{ field: 'approval_policy', order: ['always', 'untrusted', 'on-request', 'never'], minimum: 'on-request' },
|
|
421
|
-
{ field: 'sandbox_mode', order: ['full', 'read-only', 'workspace-write', 'off'], minimum: 'workspace-write' },
|
|
422
|
-
],
|
|
423
|
-
}, 'user');
|
|
424
|
-
}
|
|
425
|
-
catch {
|
|
426
|
-
// Best-effort
|
|
427
|
-
}
|
|
515
|
+
state.agentDefaultsVersion = AGENT_DEFAULTS_SCHEMA_VERSION;
|
|
428
516
|
}
|
|
429
517
|
// Register ~/.runwork as a project in the Codex desktop app (best-effort).
|
|
430
518
|
// Only attempts when Codex adapter is configured and the desktop app is closed.
|