webmux 0.28.1 → 0.29.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/bin/webmux.js CHANGED
@@ -7190,7 +7190,7 @@ var init_index_esm = __esm(() => {
7190
7190
  });
7191
7191
 
7192
7192
  // packages/api-contract/src/schemas.ts
7193
- var BooleanLikeSchema, ErrorResponseSchema, OkResponseSchema, EnabledResponseSchema, AgentKindSchema, CreateWorktreeAgentSelectionSchema, WorktreeCreateModeSchema, WorktreeCreationPhaseSchema, AvailableBranchSchema, AvailableBranchesQuerySchema, NumberLikePathParamSchema, BranchListResponseSchema, CreateWorktreeRequestSchema, CreateWorktreeResponseSchema, SetWorktreeArchivedRequestSchema, SetWorktreeArchivedResponseSchema, ToggleEnabledRequestSchema, SendWorktreePromptRequestSchema, AgentsSendMessageRequestSchema, PullMainRequestSchema, PullMainStatusSchema, PullMainResponseSchema, ServiceStatusSchema, PrCommentSchema, CiCheckSchema, PrEntrySchema, LinearIssueLabelSchema, LinearIssueStateSchema, LinkedLinearIssueSchema, LinearIssueSchema, LinearIssueAvailabilitySchema, LinearIssuesResponseSchema, WorktreeCreationStateSchema, AppNotificationSchema, ProjectWorktreeSnapshotSchema, ProjectSnapshotSchema, WorktreeConversationProviderSchema, CodexWorktreeConversationRefSchema, ClaudeWorktreeConversationRefSchema, WorktreeConversationRefSchema, AgentsUiWorktreeSummarySchema, AgentsUiConversationMessageRoleSchema, AgentsUiConversationMessageStatusSchema, AgentsUiConversationMessageSchema, AgentsUiConversationStateSchema, AgentsUiWorktreeConversationResponseSchema, AgentsUiSendMessageResponseSchema, AgentsUiInterruptResponseSchema, AgentsUiConversationSnapshotEventSchema, AgentsUiConversationMessageDeltaEventSchema, AgentsUiConversationErrorEventSchema, AgentsUiConversationEventSchema, WorktreeListResponseSchema, UnpushedCommitSchema, WorktreeDiffResponseSchema, ServiceConfigSchema, ProfileConfigSchema, LinkedRepoInfoSchema, AppConfigSchema, CiLogsResponseSchema, WorktreeNameParamsSchema, NotificationIdParamsSchema, RunIdParamsSchema;
7193
+ var BooleanLikeSchema, ErrorResponseSchema, OkResponseSchema, EnabledResponseSchema, BuiltInAgentIdSchema, AgentIdSchema, WorktreeCreateModeSchema, AgentCapabilitiesSchema, AgentSummarySchema, AgentDetailsSchema, AgentListResponseSchema, UpsertCustomAgentRequestSchema, AgentResponseSchema, ValidateCustomAgentResponseSchema, WorktreeCreationPhaseSchema, AvailableBranchSchema, AvailableBranchesQuerySchema, NumberLikePathParamSchema, BranchListResponseSchema, CreateWorktreeRequestSchema, CreateWorktreeResponseSchema, SetWorktreeArchivedRequestSchema, SetWorktreeArchivedResponseSchema, ToggleEnabledRequestSchema, SendWorktreePromptRequestSchema, AgentsSendMessageRequestSchema, PullMainRequestSchema, PullMainStatusSchema, PullMainResponseSchema, ServiceStatusSchema, PrCommentSchema, CiCheckSchema, PrEntrySchema, LinearIssueLabelSchema, LinearIssueStateSchema, LinkedLinearIssueSchema, LinearIssueSchema, LinearIssueAvailabilitySchema, LinearIssuesResponseSchema, WorktreeCreationStateSchema, AppNotificationSchema, ProjectWorktreeSnapshotSchema, ProjectSnapshotSchema, WorktreeConversationProviderSchema, CodexWorktreeConversationRefSchema, ClaudeWorktreeConversationRefSchema, WorktreeConversationRefSchema, AgentsUiWorktreeSummarySchema, AgentsUiConversationMessageRoleSchema, AgentsUiConversationMessageStatusSchema, AgentsUiConversationMessageSchema, AgentsUiConversationStateSchema, AgentsUiWorktreeConversationResponseSchema, AgentsUiSendMessageResponseSchema, AgentsUiInterruptResponseSchema, AgentsUiConversationSnapshotEventSchema, AgentsUiConversationMessageDeltaEventSchema, AgentsUiConversationErrorEventSchema, AgentsUiConversationEventSchema, WorktreeListResponseSchema, UnpushedCommitSchema, WorktreeDiffResponseSchema, ServiceConfigSchema, ProfileConfigSchema, LinkedRepoInfoSchema, AppConfigSchema, CiLogsResponseSchema, WorktreeNameParamsSchema, NotificationIdParamsSchema, AgentIdParamsSchema, RunIdParamsSchema;
7194
7194
  var init_schemas = __esm(() => {
7195
7195
  init_zod();
7196
7196
  BooleanLikeSchema = exports_external.union([
@@ -7208,9 +7208,45 @@ var init_schemas = __esm(() => {
7208
7208
  ok: exports_external.literal(true),
7209
7209
  enabled: exports_external.boolean()
7210
7210
  });
7211
- AgentKindSchema = exports_external.enum(["claude", "codex"]);
7212
- CreateWorktreeAgentSelectionSchema = exports_external.enum(["claude", "codex", "both"]);
7211
+ BuiltInAgentIdSchema = exports_external.enum(["claude", "codex"]);
7212
+ AgentIdSchema = exports_external.string().trim().min(1);
7213
7213
  WorktreeCreateModeSchema = exports_external.enum(["new", "existing"]);
7214
+ AgentCapabilitiesSchema = exports_external.object({
7215
+ terminal: exports_external.literal(true),
7216
+ inAppChat: exports_external.boolean(),
7217
+ conversationHistory: exports_external.boolean(),
7218
+ interrupt: exports_external.boolean(),
7219
+ resume: exports_external.boolean()
7220
+ });
7221
+ AgentSummarySchema = exports_external.object({
7222
+ id: AgentIdSchema,
7223
+ label: exports_external.string(),
7224
+ kind: exports_external.enum(["builtin", "custom"]),
7225
+ capabilities: AgentCapabilitiesSchema
7226
+ });
7227
+ AgentDetailsSchema = exports_external.object({
7228
+ id: AgentIdSchema,
7229
+ label: exports_external.string(),
7230
+ kind: exports_external.enum(["builtin", "custom"]),
7231
+ capabilities: AgentCapabilitiesSchema,
7232
+ startCommand: exports_external.string().nullable(),
7233
+ resumeCommand: exports_external.string().nullable()
7234
+ });
7235
+ AgentListResponseSchema = exports_external.object({
7236
+ agents: exports_external.array(AgentDetailsSchema)
7237
+ });
7238
+ UpsertCustomAgentRequestSchema = exports_external.object({
7239
+ label: exports_external.string().trim().min(1),
7240
+ startCommand: exports_external.string().trim().min(1),
7241
+ resumeCommand: exports_external.string().trim().optional()
7242
+ });
7243
+ AgentResponseSchema = exports_external.object({
7244
+ agent: AgentDetailsSchema
7245
+ });
7246
+ ValidateCustomAgentResponseSchema = exports_external.object({
7247
+ normalizedId: AgentIdSchema,
7248
+ warnings: exports_external.array(exports_external.string())
7249
+ });
7214
7250
  WorktreeCreationPhaseSchema = exports_external.enum([
7215
7251
  "creating_worktree",
7216
7252
  "preparing_runtime",
@@ -7236,7 +7272,8 @@ var init_schemas = __esm(() => {
7236
7272
  branch: exports_external.string().optional(),
7237
7273
  baseBranch: exports_external.string().optional(),
7238
7274
  profile: exports_external.string().optional(),
7239
- agent: CreateWorktreeAgentSelectionSchema.optional(),
7275
+ agent: AgentIdSchema.optional(),
7276
+ agents: exports_external.array(AgentIdSchema).min(1).optional(),
7240
7277
  prompt: exports_external.string().optional(),
7241
7278
  envOverrides: exports_external.record(exports_external.string()).optional(),
7242
7279
  createLinearTicket: exports_external.literal(true).optional(),
@@ -7367,7 +7404,8 @@ var init_schemas = __esm(() => {
7367
7404
  dir: exports_external.string(),
7368
7405
  archived: exports_external.boolean(),
7369
7406
  profile: exports_external.string().nullable(),
7370
- agentName: AgentKindSchema.nullable(),
7407
+ agentName: AgentIdSchema.nullable(),
7408
+ agentLabel: exports_external.string().nullable(),
7371
7409
  mux: exports_external.boolean(),
7372
7410
  dirty: exports_external.boolean(),
7373
7411
  unpushed: exports_external.boolean(),
@@ -7412,7 +7450,8 @@ var init_schemas = __esm(() => {
7412
7450
  path: exports_external.string(),
7413
7451
  archived: exports_external.boolean(),
7414
7452
  profile: exports_external.string().nullable(),
7415
- agentName: AgentKindSchema.nullable(),
7453
+ agentName: AgentIdSchema.nullable(),
7454
+ agentLabel: exports_external.string().nullable(),
7416
7455
  mux: exports_external.boolean(),
7417
7456
  status: exports_external.string(),
7418
7457
  dirty: exports_external.boolean(),
@@ -7504,7 +7543,9 @@ var init_schemas = __esm(() => {
7504
7543
  name: exports_external.string(),
7505
7544
  services: exports_external.array(ServiceConfigSchema),
7506
7545
  profiles: exports_external.array(ProfileConfigSchema),
7546
+ agents: exports_external.array(AgentSummarySchema),
7507
7547
  defaultProfileName: exports_external.string(),
7548
+ defaultAgentId: BuiltInAgentIdSchema,
7508
7549
  autoName: exports_external.boolean(),
7509
7550
  linearCreateTicketOption: exports_external.boolean(),
7510
7551
  startupEnvs: exports_external.record(exports_external.union([exports_external.string(), exports_external.boolean()])),
@@ -7523,6 +7564,9 @@ var init_schemas = __esm(() => {
7523
7564
  NotificationIdParamsSchema = exports_external.object({
7524
7565
  id: NumberLikePathParamSchema
7525
7566
  });
7567
+ AgentIdParamsSchema = exports_external.object({
7568
+ id: AgentIdSchema
7569
+ });
7526
7570
  RunIdParamsSchema = exports_external.object({
7527
7571
  runId: NumberLikePathParamSchema
7528
7572
  });
@@ -7539,6 +7583,11 @@ var init_contract = __esm(() => {
7539
7583
  fetchAvailableBranches: "/api/branches",
7540
7584
  fetchBaseBranches: "/api/base-branches",
7541
7585
  fetchProject: "/api/project",
7586
+ fetchAgents: "/api/agents",
7587
+ createAgent: "/api/agents",
7588
+ updateAgent: "/api/agents/:id",
7589
+ deleteAgent: "/api/agents/:id",
7590
+ validateAgent: "/api/agents/validate",
7542
7591
  attachAgentsWorktreeConversation: "/api/agents/worktrees/:name/attach",
7543
7592
  fetchAgentsWorktreeConversationHistory: "/api/agents/worktrees/:name/history",
7544
7593
  sendAgentsWorktreeConversationMessage: "/api/agents/worktrees/:name/messages",
@@ -7603,6 +7652,60 @@ var init_contract = __esm(() => {
7603
7652
  502: ErrorResponseSchema
7604
7653
  }
7605
7654
  },
7655
+ fetchAgents: {
7656
+ method: "GET",
7657
+ path: apiPaths.fetchAgents,
7658
+ responses: {
7659
+ 200: AgentListResponseSchema,
7660
+ 500: ErrorResponseSchema
7661
+ }
7662
+ },
7663
+ createAgent: {
7664
+ method: "POST",
7665
+ path: apiPaths.createAgent,
7666
+ body: UpsertCustomAgentRequestSchema,
7667
+ responses: {
7668
+ 200: AgentResponseSchema,
7669
+ 400: ErrorResponseSchema,
7670
+ 409: ErrorResponseSchema,
7671
+ 500: ErrorResponseSchema
7672
+ }
7673
+ },
7674
+ updateAgent: {
7675
+ method: "PUT",
7676
+ path: apiPaths.updateAgent,
7677
+ pathParams: AgentIdParamsSchema,
7678
+ body: UpsertCustomAgentRequestSchema,
7679
+ responses: {
7680
+ 200: AgentResponseSchema,
7681
+ 400: ErrorResponseSchema,
7682
+ 404: ErrorResponseSchema,
7683
+ 409: ErrorResponseSchema,
7684
+ 500: ErrorResponseSchema
7685
+ }
7686
+ },
7687
+ deleteAgent: {
7688
+ method: "DELETE",
7689
+ path: apiPaths.deleteAgent,
7690
+ pathParams: AgentIdParamsSchema,
7691
+ body: c2.noBody(),
7692
+ responses: {
7693
+ 200: OkResponseSchema,
7694
+ 400: ErrorResponseSchema,
7695
+ 404: ErrorResponseSchema,
7696
+ 500: ErrorResponseSchema
7697
+ }
7698
+ },
7699
+ validateAgent: {
7700
+ method: "POST",
7701
+ path: apiPaths.validateAgent,
7702
+ body: UpsertCustomAgentRequestSchema,
7703
+ responses: {
7704
+ 200: ValidateCustomAgentResponseSchema,
7705
+ 400: ErrorResponseSchema,
7706
+ 500: ErrorResponseSchema
7707
+ }
7708
+ },
7606
7709
  attachAgentsWorktreeConversation: {
7607
7710
  method: "POST",
7608
7711
  path: apiPaths.attachAgentsWorktreeConversation,
@@ -15342,6 +15445,38 @@ function parseProfiles(raw, includeDefaultProfile) {
15342
15445
  }
15343
15446
  return profiles;
15344
15447
  }
15448
+ function cloneAgentConfig(agent) {
15449
+ return { ...agent };
15450
+ }
15451
+ function cloneAgents(agents) {
15452
+ return Object.fromEntries(Object.entries(agents).map(([id, agent]) => [id, cloneAgentConfig(agent)]));
15453
+ }
15454
+ function parseCustomAgent(raw) {
15455
+ if (!isRecord4(raw))
15456
+ return null;
15457
+ if (typeof raw.label !== "string" || !raw.label.trim())
15458
+ return null;
15459
+ if (typeof raw.startCommand !== "string" || !raw.startCommand.trim())
15460
+ return null;
15461
+ return {
15462
+ label: raw.label.trim(),
15463
+ startCommand: raw.startCommand.trim(),
15464
+ ...typeof raw.resumeCommand === "string" && raw.resumeCommand.trim() ? { resumeCommand: raw.resumeCommand.trim() } : {}
15465
+ };
15466
+ }
15467
+ function parseCustomAgents(raw) {
15468
+ if (!isRecord4(raw))
15469
+ return {};
15470
+ return Object.entries(raw).reduce((acc, [id, value]) => {
15471
+ if (!id.trim())
15472
+ return acc;
15473
+ const parsed = parseCustomAgent(value);
15474
+ if (parsed) {
15475
+ acc[id.trim()] = parsed;
15476
+ }
15477
+ return acc;
15478
+ }, {});
15479
+ }
15345
15480
  function parseServices(raw) {
15346
15481
  if (!Array.isArray(raw))
15347
15482
  return [];
@@ -15434,6 +15569,7 @@ function parseProjectConfig(parsed) {
15434
15569
  autoPull: isRecord4(parsed.workspace) ? parseAutoPull(parsed.workspace.autoPull) : DEFAULT_CONFIG.workspace.autoPull
15435
15570
  },
15436
15571
  profiles: parseProfiles(parsed.profiles, true),
15572
+ agents: {},
15437
15573
  services: parseServices(parsed.services),
15438
15574
  startupEnvs: parseStartupEnvs(parsed.startupEnvs),
15439
15575
  integrations: {
@@ -15501,20 +15637,21 @@ function loadLocalProjectConfigOverlay(root) {
15501
15637
  try {
15502
15638
  const text = readLocalConfigFile(root).trim();
15503
15639
  if (!text) {
15504
- return { worktreeRoot: null, profiles: {}, lifecycleHooks: {}, linear: null, github: null, autoPull: null };
15640
+ return { worktreeRoot: null, profiles: {}, agents: {}, lifecycleHooks: {}, linear: null, github: null, autoPull: null };
15505
15641
  }
15506
15642
  const parsed = parseConfigDocument(text);
15507
15643
  const ws = isRecord4(parsed.workspace) ? parsed.workspace : null;
15508
15644
  return {
15509
15645
  worktreeRoot: ws && typeof ws.worktreeRoot === "string" ? ws.worktreeRoot : null,
15510
15646
  profiles: parseProfiles(parsed.profiles, false),
15647
+ agents: parseCustomAgents(parsed.agents),
15511
15648
  lifecycleHooks: parseLifecycleHooks(parsed.lifecycleHooks),
15512
15649
  linear: parseLocalLinearOverlay(parsed),
15513
15650
  github: parseLocalGitHubOverlay(parsed),
15514
15651
  autoPull: parseLocalAutoPullOverlay(parsed)
15515
15652
  };
15516
15653
  } catch {
15517
- return { worktreeRoot: null, profiles: {}, lifecycleHooks: {}, linear: null, github: null, autoPull: null };
15654
+ return { worktreeRoot: null, profiles: {}, agents: {}, lifecycleHooks: {}, linear: null, github: null, autoPull: null };
15518
15655
  }
15519
15656
  }
15520
15657
  function mergeHookCommand(projectCommand, localCommand) {
@@ -15574,6 +15711,10 @@ function loadConfig(dir, options = {}) {
15574
15711
  ...cloneProfiles(projectConfig.profiles),
15575
15712
  ...cloneProfiles(localOverlay.profiles)
15576
15713
  },
15714
+ agents: {
15715
+ ...cloneAgents(projectConfig.agents),
15716
+ ...cloneAgents(localOverlay.agents)
15717
+ },
15577
15718
  lifecycleHooks: mergeLifecycleHooks(projectConfig.lifecycleHooks, localOverlay.lifecycleHooks),
15578
15719
  integrations
15579
15720
  };
@@ -15603,6 +15744,7 @@ var init_config = __esm(() => {
15603
15744
  panes: clonePanes(DEFAULT_PANES)
15604
15745
  }
15605
15746
  },
15747
+ agents: {},
15606
15748
  services: [],
15607
15749
  startupEnvs: {},
15608
15750
  integrations: {
@@ -16184,7 +16326,7 @@ class AutoNameService {
16184
16326
  return normalizeGeneratedBranchName(output);
16185
16327
  }
16186
16328
  }
16187
- var MAX_BRANCH_LENGTH = 40, DEFAULT_AUTO_NAME_MODEL = "claude-haiku-4-5-20251001", AUTO_NAME_TIMEOUT_MS = 1e4, DEFAULT_SYSTEM_PROMPT, AutoNameTimeoutError;
16329
+ var MAX_BRANCH_LENGTH = 40, DEFAULT_AUTO_NAME_MODEL = "claude-haiku-4-5-20251001", AUTO_NAME_TIMEOUT_MS = 15000, DEFAULT_SYSTEM_PROMPT, AutoNameTimeoutError;
16188
16330
  var init_auto_name_service = __esm(() => {
16189
16331
  init_policies();
16190
16332
  init_branch_name();
@@ -16563,7 +16705,7 @@ function buildRuntimeBootstrap(runtimeEnvPath) {
16563
16705
  function buildDockerRuntimeBootstrap(runtimeEnvPath) {
16564
16706
  return `${buildRuntimeBootstrap(runtimeEnvPath)}; export PATH="$PATH:${DOCKER_PATH_FALLBACK}"`;
16565
16707
  }
16566
- function buildAgentInvocation(input) {
16708
+ function buildBuiltInAgentInvocation(input) {
16567
16709
  if (input.agent === "codex") {
16568
16710
  const yoloFlag2 = input.yolo ? " --yolo" : "";
16569
16711
  if (input.launchMode === "resume") {
@@ -16585,6 +16727,47 @@ function buildAgentInvocation(input) {
16585
16727
  }
16586
16728
  return `claude${yoloFlag}${promptSuffix}`;
16587
16729
  }
16730
+ function renderCustomCommandTemplate(template) {
16731
+ return template.replaceAll("${PROMPT}", `$${CUSTOM_AGENT_TEMPLATE_VARS.PROMPT}`).replaceAll("${SYSTEM_PROMPT}", `$${CUSTOM_AGENT_TEMPLATE_VARS.SYSTEM_PROMPT}`).replaceAll("${WORKTREE_PATH}", `$${CUSTOM_AGENT_TEMPLATE_VARS.WORKTREE_PATH}`).replaceAll("${REPO_PATH}", `$${CUSTOM_AGENT_TEMPLATE_VARS.REPO_PATH}`).replaceAll("${BRANCH}", `$${CUSTOM_AGENT_TEMPLATE_VARS.BRANCH}`).replaceAll("${PROFILE}", `$${CUSTOM_AGENT_TEMPLATE_VARS.PROFILE}`);
16732
+ }
16733
+ function buildCustomAgentExports(input) {
16734
+ const envEntries = [
16735
+ [CUSTOM_AGENT_TEMPLATE_VARS.PROMPT, input.prompt ?? ""],
16736
+ [CUSTOM_AGENT_TEMPLATE_VARS.SYSTEM_PROMPT, input.systemPrompt ?? ""],
16737
+ [CUSTOM_AGENT_TEMPLATE_VARS.WORKTREE_PATH, input.worktreePath],
16738
+ [CUSTOM_AGENT_TEMPLATE_VARS.REPO_PATH, input.repoRoot],
16739
+ [CUSTOM_AGENT_TEMPLATE_VARS.BRANCH, input.branch],
16740
+ [CUSTOM_AGENT_TEMPLATE_VARS.PROFILE, input.profileName]
16741
+ ];
16742
+ return envEntries.map(([key, value]) => `export ${key}=${quoteShell(value)}`).join("; ");
16743
+ }
16744
+ function buildCustomAgentInvocation(input) {
16745
+ const template = input.launchMode === "resume" && input.agent.implementation.config.resumeCommand ? input.agent.implementation.config.resumeCommand : input.agent.implementation.config.startCommand;
16746
+ const exports = buildCustomAgentExports(input);
16747
+ const renderedCommand = renderCustomCommandTemplate(template);
16748
+ return `${exports}; ${renderedCommand}`;
16749
+ }
16750
+ function buildAgentInvocation(input) {
16751
+ if (input.agent.kind === "builtin") {
16752
+ return buildBuiltInAgentInvocation({
16753
+ agent: input.agent.implementation.agent,
16754
+ yolo: input.yolo,
16755
+ systemPrompt: input.systemPrompt,
16756
+ prompt: input.prompt,
16757
+ launchMode: input.launchMode
16758
+ });
16759
+ }
16760
+ return buildCustomAgentInvocation({
16761
+ agent: input.agent,
16762
+ systemPrompt: input.systemPrompt,
16763
+ prompt: input.prompt,
16764
+ worktreePath: input.worktreePath,
16765
+ repoRoot: input.repoRoot,
16766
+ branch: input.branch,
16767
+ profileName: input.profileName,
16768
+ launchMode: input.launchMode
16769
+ });
16770
+ }
16588
16771
  function buildAgentCommand(input, bootstrap = buildRuntimeBootstrap) {
16589
16772
  return `${bootstrap(input.runtimeEnvPath)}; ${buildAgentInvocation(input)}`;
16590
16773
  }
@@ -16603,7 +16786,109 @@ function buildDockerShellCommand(containerName2, worktreePath, runtimeEnvPath, s
16603
16786
  function buildDockerAgentPaneCommand(input) {
16604
16787
  return buildAgentCommand(input, buildDockerRuntimeBootstrap);
16605
16788
  }
16606
- var DOCKER_PATH_FALLBACK = "/root/.local/bin:/usr/local/bin:/root/.bun/bin:/root/.cargo/bin";
16789
+ var DOCKER_PATH_FALLBACK = "/root/.local/bin:/usr/local/bin:/root/.bun/bin:/root/.cargo/bin", CUSTOM_AGENT_TEMPLATE_VARS;
16790
+ var init_agent_service = __esm(() => {
16791
+ CUSTOM_AGENT_TEMPLATE_VARS = {
16792
+ PROMPT: "WEBMUX_AGENT_PROMPT",
16793
+ SYSTEM_PROMPT: "WEBMUX_AGENT_SYSTEM_PROMPT",
16794
+ WORKTREE_PATH: "WEBMUX_AGENT_WORKTREE_PATH",
16795
+ REPO_PATH: "WEBMUX_AGENT_REPO_PATH",
16796
+ BRANCH: "WEBMUX_AGENT_BRANCH",
16797
+ PROFILE: "WEBMUX_AGENT_PROFILE"
16798
+ };
16799
+ });
16800
+
16801
+ // backend/src/services/agent-registry.ts
16802
+ function cloneCapabilities(capabilities) {
16803
+ return { ...capabilities };
16804
+ }
16805
+ function cloneDefinition(definition) {
16806
+ if (definition.kind === "builtin") {
16807
+ return {
16808
+ ...definition,
16809
+ capabilities: cloneCapabilities(definition.capabilities),
16810
+ implementation: { ...definition.implementation }
16811
+ };
16812
+ }
16813
+ return {
16814
+ ...definition,
16815
+ capabilities: cloneCapabilities(definition.capabilities),
16816
+ implementation: {
16817
+ type: "custom",
16818
+ config: { ...definition.implementation.config }
16819
+ }
16820
+ };
16821
+ }
16822
+ function buildCustomAgentDefinition(id, config) {
16823
+ return {
16824
+ id,
16825
+ label: config.label,
16826
+ kind: "custom",
16827
+ capabilities: {
16828
+ terminal: true,
16829
+ inAppChat: false,
16830
+ conversationHistory: false,
16831
+ interrupt: false,
16832
+ resume: config.resumeCommand !== undefined
16833
+ },
16834
+ implementation: {
16835
+ type: "custom",
16836
+ config: { ...config }
16837
+ }
16838
+ };
16839
+ }
16840
+ function listAgentDefinitions(config) {
16841
+ const builtInIds = new Set(BUILTIN_AGENT_DEFINITIONS.map((agent) => agent.id));
16842
+ const customDefinitions = Object.entries(config.agents).filter(([id]) => !builtInIds.has(id)).sort(([leftId, left], [rightId, right]) => {
16843
+ const labelCompare = left.label.localeCompare(right.label);
16844
+ return labelCompare !== 0 ? labelCompare : leftId.localeCompare(rightId);
16845
+ }).map(([id, agent]) => buildCustomAgentDefinition(id, agent));
16846
+ return [
16847
+ ...BUILTIN_AGENT_DEFINITIONS.map((definition) => cloneDefinition(definition)),
16848
+ ...customDefinitions
16849
+ ];
16850
+ }
16851
+ function getAgentDefinition(config, agentId) {
16852
+ const definition = listAgentDefinitions(config).find((agent) => agent.id === agentId);
16853
+ return definition ?? null;
16854
+ }
16855
+ var BUILTIN_AGENT_DEFINITIONS;
16856
+ var init_agent_registry = __esm(() => {
16857
+ BUILTIN_AGENT_DEFINITIONS = [
16858
+ {
16859
+ id: "claude",
16860
+ label: "Claude",
16861
+ kind: "builtin",
16862
+ capabilities: {
16863
+ terminal: true,
16864
+ inAppChat: true,
16865
+ conversationHistory: true,
16866
+ interrupt: true,
16867
+ resume: true
16868
+ },
16869
+ implementation: {
16870
+ type: "builtin",
16871
+ agent: "claude"
16872
+ }
16873
+ },
16874
+ {
16875
+ id: "codex",
16876
+ label: "Codex",
16877
+ kind: "builtin",
16878
+ capabilities: {
16879
+ terminal: true,
16880
+ inAppChat: true,
16881
+ conversationHistory: true,
16882
+ interrupt: true,
16883
+ resume: true
16884
+ },
16885
+ implementation: {
16886
+ type: "builtin",
16887
+ agent: "codex"
16888
+ }
16889
+ }
16890
+ ];
16891
+ });
16607
16892
 
16608
16893
  // backend/src/services/session-service.ts
16609
16894
  import { resolve as resolve6 } from "path";
@@ -16880,14 +17165,15 @@ function buildRuntimeControlBaseUrl(controlBaseUrl, runtime) {
16880
17165
  function prefixAgentBranch(agent, branch) {
16881
17166
  return `${agent}-${branch}`;
16882
17167
  }
16883
- function buildCreateWorktreeTargets(branch, agentSelection) {
16884
- if (agentSelection === "both") {
16885
- return [
16886
- { branch: prefixAgentBranch("claude", branch), agent: "claude" },
16887
- { branch: prefixAgentBranch("codex", branch), agent: "codex" }
16888
- ];
17168
+ function buildCreateWorktreeTargets(branch, agentIds) {
17169
+ if (agentIds.length <= 1) {
17170
+ const agent = agentIds[0];
17171
+ return agent ? [{ branch, agent }] : [];
16889
17172
  }
16890
- return [{ branch, agent: agentSelection }];
17173
+ return agentIds.map((agent) => ({
17174
+ branch: prefixAgentBranch(agent, branch),
17175
+ agent
17176
+ }));
16891
17177
  }
16892
17178
 
16893
17179
  class LifecycleService {
@@ -16897,12 +17183,12 @@ class LifecycleService {
16897
17183
  }
16898
17184
  async createWorktrees(input) {
16899
17185
  const mode = input.mode ?? "new";
16900
- const agentSelection = input.agent ?? this.deps.config.workspace.defaultAgent;
16901
- if (agentSelection === "both" && mode === "existing") {
16902
- throw new LifecycleError("Creating both agents is only supported for new worktrees", 400);
17186
+ const agentIds = this.resolveSelectedAgents(input);
17187
+ if (agentIds.length > 1 && mode === "existing") {
17188
+ throw new LifecycleError("Creating multiple agents is only supported for new worktrees", 400);
16903
17189
  }
16904
17190
  const branch = await this.resolveBranch(input.branch, input.prompt, mode);
16905
- const targets = buildCreateWorktreeTargets(branch, agentSelection);
17191
+ const targets = buildCreateWorktreeTargets(branch, agentIds);
16906
17192
  const createdBranches = [];
16907
17193
  try {
16908
17194
  for (const target of targets) {
@@ -16929,28 +17215,30 @@ class LifecycleService {
16929
17215
  async createWorktree(input) {
16930
17216
  const mode = input.mode ?? "new";
16931
17217
  const branch = await this.resolveBranch(input.branch, input.prompt, mode);
16932
- const agent = this.resolveAgent(input.agent);
17218
+ const agent = this.resolveAgentDefinition(input.agent);
16933
17219
  return await this.createResolvedWorktree({
16934
17220
  ...input,
16935
17221
  mode,
16936
17222
  branch,
16937
- agent
17223
+ agent: agent.id
16938
17224
  });
16939
17225
  }
16940
17226
  async openWorktree(branch) {
16941
17227
  try {
16942
17228
  const resolved = await this.resolveExistingWorktree(branch);
16943
- const launchMode = resolved.meta ? "resume" : "fresh";
16944
17229
  const initialized = resolved.meta ? await this.refreshManagedArtifacts(resolved) : await this.initializeUnmanagedWorktree(resolved);
16945
- const { profile } = this.resolveProfile(initialized.meta.profile);
17230
+ const { profileName, profile } = this.resolveProfile(initialized.meta.profile);
17231
+ const agent = this.resolveAgentDefinition(initialized.meta.agent);
17232
+ const launchMode = resolved.meta && agent.capabilities.resume ? "resume" : "fresh";
16946
17233
  await ensureAgentRuntimeArtifacts({
16947
17234
  gitDir: initialized.paths.gitDir,
16948
17235
  worktreePath: resolved.entry.path
16949
17236
  });
16950
17237
  await this.materializeRuntimeSession({
16951
17238
  branch,
17239
+ profileName,
16952
17240
  profile,
16953
- agent: initialized.meta.agent,
17241
+ agent,
16954
17242
  initialized,
16955
17243
  worktreePath: resolved.entry.path,
16956
17244
  launchMode
@@ -17084,14 +17372,22 @@ class LifecycleService {
17084
17372
  profile
17085
17373
  };
17086
17374
  }
17087
- resolveAgent(agent) {
17088
- if (!agent)
17089
- return this.deps.config.workspace.defaultAgent;
17090
- if (agent !== "claude" && agent !== "codex") {
17091
- throw new LifecycleError(`Unknown agent: ${agent}`, 400);
17375
+ resolveAgentDefinition(agentId) {
17376
+ const resolvedAgentId = agentId ?? this.deps.config.workspace.defaultAgent;
17377
+ const agent = getAgentDefinition(this.deps.config, resolvedAgentId);
17378
+ if (!agent) {
17379
+ throw new LifecycleError(`Unknown agent: ${resolvedAgentId}`, 400);
17092
17380
  }
17093
17381
  return agent;
17094
17382
  }
17383
+ resolveSelectedAgents(input) {
17384
+ const selectedAgents = input.agents && input.agents.length > 0 ? input.agents : [input.agent ?? this.deps.config.workspace.defaultAgent];
17385
+ const dedupedAgentIds = [...new Set(selectedAgents.map((agent) => agent.trim()).filter((agent) => agent.length > 0))];
17386
+ if (dedupedAgentIds.length === 0) {
17387
+ throw new LifecycleError("At least one agent must be selected", 400);
17388
+ }
17389
+ return dedupedAgentIds.map((agentId) => this.resolveAgentDefinition(agentId).id);
17390
+ }
17095
17391
  async buildStartupEnvValues(envOverrides) {
17096
17392
  const startupEnvValues = Object.fromEntries(Object.entries(this.deps.config.startupEnvs).map(([key, value]) => [key, stringifyStartupEnvValue(value)]));
17097
17393
  for (const [key, value] of Object.entries(envOverrides ?? {})) {
@@ -17213,6 +17509,7 @@ class LifecycleService {
17213
17509
  });
17214
17510
  ensureSessionLayout(this.deps.tmux, this.buildSessionLayout({
17215
17511
  branch: input.branch,
17512
+ profileName: input.profileName,
17216
17513
  profile: input.profile,
17217
17514
  agent: input.agent,
17218
17515
  initialized: input.initialized,
@@ -17225,6 +17522,7 @@ class LifecycleService {
17225
17522
  }
17226
17523
  ensureSessionLayout(this.deps.tmux, this.buildSessionLayout({
17227
17524
  branch: input.branch,
17525
+ profileName: input.profileName,
17228
17526
  profile: input.profile,
17229
17527
  agent: input.agent,
17230
17528
  initialized: input.initialized,
@@ -17243,6 +17541,10 @@ class LifecycleService {
17243
17541
  agent: buildDockerAgentPaneCommand({
17244
17542
  agent: input.agent,
17245
17543
  runtimeEnvPath: input.initialized.paths.runtimeEnvPath,
17544
+ repoRoot: this.deps.projectRoot,
17545
+ worktreePath: input.worktreePath,
17546
+ branch: input.branch,
17547
+ profileName: input.profileName,
17246
17548
  yolo: input.profile.yolo === true,
17247
17549
  systemPrompt,
17248
17550
  prompt: input.launchMode === "fresh" ? input.prompt : undefined,
@@ -17253,6 +17555,10 @@ class LifecycleService {
17253
17555
  agent: buildAgentPaneCommand({
17254
17556
  agent: input.agent,
17255
17557
  runtimeEnvPath: input.initialized.paths.runtimeEnvPath,
17558
+ repoRoot: this.deps.projectRoot,
17559
+ worktreePath: input.worktreePath,
17560
+ branch: input.branch,
17561
+ profileName: input.profileName,
17256
17562
  yolo: input.profile.yolo === true,
17257
17563
  systemPrompt,
17258
17564
  prompt: input.launchMode === "fresh" ? input.prompt : undefined,
@@ -17377,6 +17683,7 @@ class LifecycleService {
17377
17683
  const baseBranch = input.mode === "new" ? requestedBaseBranch || this.deps.config.workspace.mainBranch : undefined;
17378
17684
  const branchAvailability = this.resolveBranchAvailability(input.branch, input.mode);
17379
17685
  const { profileName, profile } = this.resolveProfile(input.profile);
17686
+ const agent = this.resolveAgentDefinition(input.agent);
17380
17687
  const worktreePath = this.resolveWorktreePath(input.branch);
17381
17688
  const createProgressBase = {
17382
17689
  branch: input.branch,
@@ -17401,7 +17708,7 @@ class LifecycleService {
17401
17708
  ...baseBranch ? { baseBranch } : {},
17402
17709
  ...branchAvailability.startPoint ? { startPoint: branchAvailability.startPoint } : {},
17403
17710
  profile: profileName,
17404
- agent: input.agent,
17711
+ agent: agent.id,
17405
17712
  runtime: profile.runtime,
17406
17713
  startupEnvValues: await this.buildStartupEnvValues(input.envOverrides),
17407
17714
  allocatedPorts: await this.allocatePorts(),
@@ -17441,8 +17748,9 @@ class LifecycleService {
17441
17748
  });
17442
17749
  await this.materializeRuntimeSession({
17443
17750
  branch: input.branch,
17751
+ profileName,
17444
17752
  profile,
17445
- agent: input.agent,
17753
+ agent,
17446
17754
  initialized,
17447
17755
  worktreePath,
17448
17756
  prompt: input.prompt,
@@ -17483,6 +17791,8 @@ var init_lifecycle_service = __esm(() => {
17483
17791
  init_config();
17484
17792
  init_tmux();
17485
17793
  init_policies();
17794
+ init_agent_service();
17795
+ init_agent_registry();
17486
17796
  init_session_service();
17487
17797
  init_worktree_service();
17488
17798
  init_log();
@@ -18048,13 +18358,13 @@ function getWorktreeCommandUsage(command) {
18048
18358
  case "add":
18049
18359
  return [
18050
18360
  "Usage:",
18051
- " webmux add [branch] [--existing] [--base <branch>] [--profile <name>] [--agent <claude|codex|both>] [--prompt <text>] [--env KEY=VALUE] [--detach]",
18361
+ " webmux add [branch] [--existing] [--base <branch>] [--profile <name>] [--agent <id>] [--prompt <text>] [--env KEY=VALUE] [--detach]",
18052
18362
  "",
18053
18363
  "Options:",
18054
18364
  " --existing Use an existing local or remote branch instead of creating a new one",
18055
18365
  " --base <branch> Base branch for a new worktree (defaults to config)",
18056
18366
  " --profile <name> Worktree profile from .webmux.yaml",
18057
- " --agent <claude|codex|both> Agent to launch (both creates paired worktrees)",
18367
+ " --agent <id> Agent id to launch (repeatable)",
18058
18368
  " --prompt <text> Initial agent prompt",
18059
18369
  " --env KEY=VALUE Runtime env override (repeatable)",
18060
18370
  " -d, --detach Create worktree without switching to it",
@@ -18129,14 +18439,16 @@ function readOptionValue(args, index, flag) {
18129
18439
  };
18130
18440
  }
18131
18441
  function parseAgent(value) {
18132
- if (value === "claude" || value === "codex" || value === "both") {
18133
- return value;
18442
+ const trimmed = value.trim();
18443
+ if (!trimmed) {
18444
+ throw new CommandUsageError("Agent id cannot be empty");
18134
18445
  }
18135
- throw new CommandUsageError(`Unknown agent: ${value}`);
18446
+ return trimmed;
18136
18447
  }
18137
18448
  function parseAddCommandArgs(args) {
18138
18449
  const input = {};
18139
18450
  const envOverrides = {};
18451
+ const selectedAgents = [];
18140
18452
  let detach = false;
18141
18453
  for (let index = 0;index < args.length; index++) {
18142
18454
  const arg = args[index];
@@ -18167,7 +18479,7 @@ function parseAddCommandArgs(args) {
18167
18479
  }
18168
18480
  if (arg === "--agent" || arg.startsWith("--agent=")) {
18169
18481
  const { value, nextIndex } = readOptionValue(args, index, "--agent");
18170
- input.agent = parseAgent(value);
18482
+ selectedAgents.push(parseAgent(value));
18171
18483
  index = nextIndex;
18172
18484
  continue;
18173
18485
  }
@@ -18195,6 +18507,9 @@ function parseAddCommandArgs(args) {
18195
18507
  }
18196
18508
  input.branch = arg;
18197
18509
  }
18510
+ if (selectedAgents.length > 0) {
18511
+ input.agents = selectedAgents;
18512
+ }
18198
18513
  if (Object.keys(envOverrides).length > 0) {
18199
18514
  input.envOverrides = envOverrides;
18200
18515
  }
@@ -18452,21 +18767,12 @@ async function runWorktreeCommand(context, deps2 = {}) {
18452
18767
  if (!parsed.input.branch && parsed.input.prompt && runtime2.config.autoName) {
18453
18768
  stdout("Generating branch name...");
18454
18769
  }
18455
- if (parsed.input.agent === "both") {
18456
- const result = await runtime2.lifecycleService.createWorktrees(parsed.input);
18457
- for (const branch2 of result.branches) {
18458
- stdout(`Created worktree ${branch2}`);
18459
- }
18460
- if (!parsed.detach) {
18461
- switchToTmuxWindow(runtime2.projectDir, result.primaryBranch);
18462
- }
18463
- } else {
18464
- const { agent, ...rest } = parsed.input;
18465
- const result = await runtime2.lifecycleService.createWorktree({ ...rest, agent });
18466
- stdout(`Created worktree ${result.branch}`);
18467
- if (!parsed.detach) {
18468
- switchToTmuxWindow(runtime2.projectDir, result.branch);
18469
- }
18770
+ const result = await runtime2.lifecycleService.createWorktrees(parsed.input);
18771
+ for (const branch2 of result.branches) {
18772
+ stdout(`Created worktree ${branch2}`);
18773
+ }
18774
+ if (!parsed.detach) {
18775
+ switchToTmuxWindow(runtime2.projectDir, result.primaryBranch);
18470
18776
  }
18471
18777
  return 0;
18472
18778
  }
@@ -18605,7 +18911,7 @@ import { fileURLToPath } from "url";
18605
18911
  // package.json
18606
18912
  var package_default = {
18607
18913
  name: "webmux",
18608
- version: "0.28.1",
18914
+ version: "0.29.0",
18609
18915
  description: "Web dashboard for workmux \u2014 browser UI with embedded terminals, PR monitoring, and CI integration",
18610
18916
  type: "module",
18611
18917
  repository: {
@@ -18629,9 +18935,7 @@ var package_default = {
18629
18935
  "frontend",
18630
18936
  "packages/*"
18631
18937
  ],
18632
- dependencies: {
18633
- "@webmux/api-contract": "workspace:*"
18634
- },
18938
+ dependencies: {},
18635
18939
  scripts: {
18636
18940
  dev: "bash dev.sh",
18637
18941
  start: "bun bin/webmux.js",
@@ -18646,6 +18950,7 @@ var package_default = {
18646
18950
  "frontend/dist/"
18647
18951
  ],
18648
18952
  devDependencies: {
18953
+ "@webmux/api-contract": "workspace:*",
18649
18954
  "@clack/prompts": "^1.1.0",
18650
18955
  "@sveltejs/vite-plugin-svelte": "^5.0.0",
18651
18956
  "@tailwindcss/vite": "^4.2.0",