@keyou007/agent-bot 0.1.12 → 0.1.13-alpha.1

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 (91) hide show
  1. package/.env.example +2 -2
  2. package/CHANGELOG.md +18 -0
  3. package/README.md +32 -13
  4. package/README.zh.md +32 -13
  5. package/config.example.yaml +0 -24
  6. package/dist/cli/AgentPrerequisites.js +262 -0
  7. package/dist/cli/AgentPrerequisites.js.map +1 -0
  8. package/dist/cli/DefaultAgentSelection.js +47 -0
  9. package/dist/cli/DefaultAgentSelection.js.map +1 -0
  10. package/dist/cli/Initializer.js +165 -2
  11. package/dist/cli/Initializer.js.map +1 -1
  12. package/dist/cli/ServerStarter.js +17 -2
  13. package/dist/cli/ServerStarter.js.map +1 -1
  14. package/dist/cli/controlProtocol.js.map +1 -1
  15. package/dist/cli/help.js +16 -6
  16. package/dist/cli/help.js.map +1 -1
  17. package/dist/cli.js +233 -4
  18. package/dist/cli.js.map +1 -1
  19. package/dist/codex/AppServerConnection.js +3 -3
  20. package/dist/codex/AppServerConnection.js.map +1 -1
  21. package/dist/codex/CodexProcessManager.js +4 -4
  22. package/dist/codex/CodexProcessManager.js.map +1 -1
  23. package/dist/codex/CodexRuntime.js +28 -16
  24. package/dist/codex/CodexRuntime.js.map +1 -1
  25. package/dist/commands/CommandRouter.js +5 -0
  26. package/dist/commands/CommandRouter.js.map +1 -1
  27. package/dist/config/FeishuUserOpenIdStore.js +76 -0
  28. package/dist/config/FeishuUserOpenIdStore.js.map +1 -0
  29. package/dist/config/loadConfig.js +3 -24
  30. package/dist/config/loadConfig.js.map +1 -1
  31. package/dist/console/ConsoleConnector.js +2 -2
  32. package/dist/console/ConsoleConnector.js.map +1 -1
  33. package/dist/feishu/CardRenderer.js +90 -30
  34. package/dist/feishu/CardRenderer.js.map +1 -1
  35. package/dist/index.js +104 -24
  36. package/dist/index.js.map +1 -1
  37. package/dist/presentation/turnGraph.js +36 -0
  38. package/dist/presentation/turnGraph.js.map +1 -0
  39. package/dist/proxy/ProxySessionController.js +321 -98
  40. package/dist/proxy/ProxySessionController.js.map +1 -1
  41. package/dist/runtime/AcpRuntimeAdapter.js +3 -2
  42. package/dist/runtime/AcpRuntimeAdapter.js.map +1 -1
  43. package/dist/startup/StartupNotifier.js +16 -7
  44. package/dist/startup/StartupNotifier.js.map +1 -1
  45. package/dist/startup/startFeishu.js +2 -2
  46. package/dist/startup/startFeishu.js.map +1 -1
  47. package/dist/state/StateStore.js +169 -0
  48. package/dist/state/StateStore.js.map +1 -1
  49. package/dist/state/migrations.js +21 -0
  50. package/dist/state/migrations.js.map +1 -1
  51. package/dist/supervision/SafeRestartNotifier.js +46 -3
  52. package/dist/supervision/SafeRestartNotifier.js.map +1 -1
  53. package/dist/supervision/SafeRestartScheduler.js +65 -6
  54. package/dist/supervision/SafeRestartScheduler.js.map +1 -1
  55. package/dist/supervision/replacementSupervisor.js +27 -2
  56. package/dist/supervision/replacementSupervisor.js.map +1 -1
  57. package/dist/supervisor.js +27 -2
  58. package/dist/supervisor.js.map +1 -1
  59. package/docs/technical-reference.md +42 -20
  60. package/docs/technical-reference.zh.md +42 -20
  61. package/npm-shrinkwrap.json +2 -2
  62. package/package.json +7 -3
  63. package/skills/agent-bot/SKILL.md +21 -9
  64. package/src/cli/AgentPrerequisites.ts +377 -0
  65. package/src/cli/DefaultAgentSelection.ts +56 -0
  66. package/src/cli/Initializer.ts +204 -3
  67. package/src/cli/ServerStarter.ts +25 -2
  68. package/src/cli/controlProtocol.ts +6 -1
  69. package/src/cli/help.ts +16 -6
  70. package/src/cli.ts +350 -5
  71. package/src/codex/AppServerConnection.ts +3 -3
  72. package/src/codex/CodexProcessManager.ts +4 -4
  73. package/src/codex/CodexRuntime.ts +38 -18
  74. package/src/commands/CommandRouter.ts +4 -0
  75. package/src/commands/commandTypes.ts +1 -0
  76. package/src/config/FeishuUserOpenIdStore.ts +81 -0
  77. package/src/config/loadConfig.ts +3 -24
  78. package/src/console/ConsoleConnector.ts +2 -2
  79. package/src/feishu/CardRenderer.ts +117 -30
  80. package/src/index.ts +162 -22
  81. package/src/presentation/turnGraph.ts +48 -0
  82. package/src/proxy/ProxySessionController.ts +375 -107
  83. package/src/runtime/AcpRuntimeAdapter.ts +3 -1
  84. package/src/startup/StartupNotifier.ts +23 -7
  85. package/src/startup/startFeishu.ts +3 -2
  86. package/src/state/StateStore.ts +221 -0
  87. package/src/state/migrations.ts +21 -0
  88. package/src/supervision/SafeRestartNotifier.ts +53 -4
  89. package/src/supervision/SafeRestartScheduler.ts +78 -7
  90. package/src/supervision/replacementSupervisor.ts +27 -1
  91. package/src/supervisor.ts +33 -2
package/src/index.ts CHANGED
@@ -5,6 +5,8 @@ import { LocalControlServer } from "./cli/LocalControlServer.js";
5
5
  import { controlEndpoint, type ControlRequest, type ControlResponse } from "./cli/controlProtocol.js";
6
6
  import { readPackageVersion } from "./cli/packageVersion.js";
7
7
  import { loadConfig } from "./config/loadConfig.js";
8
+ import { persistFeishuUserOpenIdIfMissing } from "./config/FeishuUserOpenIdStore.js";
9
+ import { defaultDotEnvPath } from "./config/paths.js";
8
10
  import { ConsoleConnector } from "./console/ConsoleConnector.js";
9
11
  import { ConsoleTurnPresenter } from "./console/ConsoleTurnPresenter.js";
10
12
  import { CardRenderer } from "./feishu/CardRenderer.js";
@@ -12,6 +14,8 @@ import { ConsoleFeishuClient } from "./feishu/ConsoleFeishuClient.js";
12
14
  import { FeishuConnector } from "./feishu/FeishuConnector.js";
13
15
  import { FeishuMessageClient } from "./feishu/FeishuMessageClient.js";
14
16
  import { FeishuTurnPresenter } from "./feishu/FeishuTurnPresenter.js";
17
+ import { baseChatContextKey, isThreadContextKey } from "./feishu/contextKey.js";
18
+ import type { MessageReplyTarget } from "./feishu/types.js";
15
19
  import { requireServerFeishuTransport } from "./feishu/transport.js";
16
20
  import { createLogger } from "./logging/logger.js";
17
21
  import { OutboundRouter, type OutboundRoute } from "./presentation/OutboundRouter.js";
@@ -22,8 +26,13 @@ import { StartupNotifier } from "./startup/StartupNotifier.js";
22
26
  import { SessionMetadataHydrator } from "./startup/SessionMetadataHydrator.js";
23
27
  import { startFeishu } from "./startup/startFeishu.js";
24
28
  import { STOP_EXIT_CODE } from "./supervision/restartPolicy.js";
25
- import { replacementSupervisorEnvironment } from "./supervision/replacementSupervisor.js";
29
+ import {
30
+ RESTART_GROUP_CONTEXTS_ENV,
31
+ replacementSupervisorEnvironment,
32
+ restartGroupContextKeysFromEnvironment,
33
+ } from "./supervision/replacementSupervisor.js";
26
34
  import { SafeRestartScheduler } from "./supervision/SafeRestartScheduler.js";
35
+ import type { RestartNotificationTarget } from "./supervision/SafeRestartScheduler.js";
27
36
  import { SafeRestartNotifier } from "./supervision/SafeRestartNotifier.js";
28
37
  import {
29
38
  nodeDiagnosticReportArguments,
@@ -37,6 +46,9 @@ const agentBotVersion = readPackageVersion();
37
46
  const supervised = process.env.AGENT_BOT_SUPERVISED === "1";
38
47
  const startupReason = process.env.AGENT_BOT_RESTART_REASON?.trim()
39
48
  || (supervised ? "Supervisor 启动" : "直接启动");
49
+ const restartGroupContextKeys = restartGroupContextKeysFromEnvironment(
50
+ process.env[RESTART_GROUP_CONTEXTS_ENV],
51
+ );
40
52
  const consoleOnly = process.env.AGENT_BOT_CONSOLE_ONLY === "1";
41
53
  const config = loadConfig();
42
54
  const transport = consoleOnly ? "console" : requireServerFeishuTransport(config.feishu);
@@ -59,7 +71,7 @@ if (feishuOutbound) {
59
71
  const presenter = new FeishuTurnPresenter(feishuOutbound, store, renderer, {
60
72
  normalIntervalMs: 2_000,
61
73
  criticalGapMs: 500,
62
- onError: (error) => logger.warn({ error }, "Failed to update Codex progress card."),
74
+ onError: (error) => logger.warn({ error }, "Failed to update Agent progress card."),
63
75
  });
64
76
  routes.push({ matches: (contextKey) => !contextKey.startsWith("console:"), outbound: feishuOutbound, presenter });
65
77
  const defaultAgentName = config.defaults.agent!;
@@ -92,7 +104,7 @@ let shuttingDown = false;
92
104
  let restartRequested = false;
93
105
  const safeRestart = new SafeRestartScheduler({
94
106
  readActivity: () => store.getServerActivityState(),
95
- onReady: (reason) => initiateRestart(reason),
107
+ onReady: (reason, notificationTargets) => initiateRestart(reason, notificationTargets),
96
108
  onStatus: (status) => safeRestartNotifier?.update(status),
97
109
  onStatusError: (error) => logger.warn({ error }, "Failed to publish safe restart status."),
98
110
  });
@@ -100,6 +112,16 @@ const controller = new ProxySessionController(config, store, runtimes, outbound,
100
112
  supervised,
101
113
  restart: requestRestart,
102
114
  cancelSafeRestart: (scheduleId) => safeRestart.cancelScheduled(scheduleId),
115
+ rememberFeishuUserOpenId: (userOpenId) => {
116
+ if (config.feishu.userOpenId?.startsWith("ou_")) return;
117
+ const persisted = persistFeishuUserOpenIdIfMissing(defaultDotEnvPath(), userOpenId);
118
+ config.feishu.userOpenId = persisted.userOpenId;
119
+ process.env.FEISHU_USER_OPEN_ID = persisted.userOpenId;
120
+ logger.info(
121
+ { status: persisted.status },
122
+ "Stored the default Lark user Open ID from the first private chat message.",
123
+ );
124
+ },
103
125
  });
104
126
 
105
127
  if (feishuOutbound) feishuConnector = new FeishuConnector(config, controller, logger);
@@ -126,6 +148,7 @@ if (feishuConnector && startupNotifier) {
126
148
  startupReason,
127
149
  startControlServer,
128
150
  () => { serverReady = true; },
151
+ restartGroupContextKeys,
129
152
  );
130
153
  } else {
131
154
  await startControlServer();
@@ -134,40 +157,59 @@ if (feishuConnector && startupNotifier) {
134
157
  }
135
158
  consoleConnector?.start();
136
159
 
137
- async function requestRestart(contextKey: string, force: boolean): Promise<void> {
160
+ async function requestRestart(
161
+ contextKey: string,
162
+ force: boolean,
163
+ replyTarget?: MessageReplyTarget,
164
+ ): Promise<void> {
165
+ const notificationTarget: RestartNotificationTarget = {
166
+ contextKey,
167
+ ...(replyTarget ? { replyMessageId: replyTarget.messageId } : {}),
168
+ };
138
169
  if (restartRequested) {
139
170
  await outbound.sendText(contextKey, "Agent Bot 已在重启中,请稍候。").catch(() => undefined);
140
171
  return;
141
172
  }
142
173
  if (force) {
143
- await initiateRestart("用户执行 /restart --force 命令", contextKey);
174
+ await initiateRestart("用户执行 /restart --force 命令", [notificationTarget]);
144
175
  return;
145
176
  }
146
- const newlyScheduled = safeRestart.schedule("用户执行 /restart 命令");
177
+ const newlyScheduled = safeRestart.schedule("用户执行 /restart 命令", notificationTarget);
147
178
  await outbound.sendText(
148
179
  contextKey,
149
180
  newlyScheduled
150
181
  ? "已安排安全重启。Agent Bot 会等待所有任务完成、最终结果投递完成,并保持 15 秒无新消息后重启。"
151
- : "安全重启已在等待中,已更新重启原因。",
182
+ : "安全重启已在等待中;当前会话已加入通知范围,并已更新重启原因。",
152
183
  ).catch((error: unknown) => {
153
184
  logger.warn({ error, contextKey }, "Failed to send safe restart acknowledgement.");
154
185
  });
155
186
  }
156
187
 
157
- async function initiateRestart(reason: string, contextKey?: string): Promise<void> {
188
+ async function initiateRestart(
189
+ reason: string,
190
+ notificationTargets: RestartNotificationTarget[] = [],
191
+ ): Promise<void> {
158
192
  if (restartRequested) {
159
- if (contextKey) await outbound.sendText(contextKey, "Agent Bot 已在重启中,请稍候。").catch(() => undefined);
193
+ await sendRestartTexts(notificationTargets, "Agent Bot 已在重启中,请稍候。");
160
194
  return;
161
195
  }
162
196
  restartRequested = true;
163
197
  await safeRestart.cancelCurrent();
164
198
  await safeRestartNotifier?.flush();
165
- if (contextKey) {
166
- await outbound.sendText(contextKey, "Agent Bot 正在重启,恢复在线后会发送启动状态通知。").catch((error: unknown) => {
167
- logger.warn({ error, contextKey }, "Failed to send restart acknowledgement.");
168
- });
169
- }
170
- setTimeout(() => void shutdown(supervised ? STOP_EXIT_CODE : 0, reason), 100);
199
+ const allNotificationTargets = mergeRestartNotificationTargets(
200
+ notificationTargets,
201
+ safeRestartNotifier?.getNotificationTargets() ?? [],
202
+ );
203
+ const restartGroupContextKeys = groupContextKeysForRestartTargets(allNotificationTargets);
204
+ await sendRestartTexts(
205
+ allNotificationTargets,
206
+ "Agent Bot 正在重启,恢复在线后会发送启动状态通知。",
207
+ );
208
+ setTimeout(() => void shutdown(
209
+ supervised ? STOP_EXIT_CODE : 0,
210
+ reason,
211
+ restartGroupContextKeys,
212
+ ), 100);
171
213
  }
172
214
 
173
215
  async function handleControlRequest(request: ControlRequest): Promise<ControlResponse> {
@@ -187,9 +229,10 @@ async function handleControlRequest(request: ControlRequest): Promise<ControlRes
187
229
  activity: store.getServerActivityState(),
188
230
  },
189
231
  };
190
- case "server_restart":
232
+ case "server_restart": {
233
+ const notificationTarget = inferControlRestartTarget(request.notificationSessionId);
191
234
  if (request.mode === "safe") {
192
- const newlyScheduled = safeRestart.schedule(request.reason);
235
+ const newlyScheduled = safeRestart.schedule(request.reason, notificationTarget);
193
236
  return {
194
237
  ok: true,
195
238
  message: newlyScheduled
@@ -197,8 +240,12 @@ async function handleControlRequest(request: ControlRequest): Promise<ControlRes
197
240
  : "A safe restart is already pending. Its reason has been updated.",
198
241
  };
199
242
  }
200
- setTimeout(() => void initiateRestart(request.reason), 25);
243
+ setTimeout(() => void initiateRestart(
244
+ request.reason,
245
+ notificationTarget ? [notificationTarget] : [],
246
+ ), 25);
201
247
  return { ok: true, message: "Immediate restart requested." };
248
+ }
202
249
  case "server_stop":
203
250
  safeRestart.cancel();
204
251
  setTimeout(() => void shutdown(STOP_EXIT_CODE), 25);
@@ -235,7 +282,11 @@ async function handleControlRequest(request: ControlRequest): Promise<ControlRes
235
282
  }
236
283
  }
237
284
 
238
- async function shutdown(exitCode: number, restartReason?: string): Promise<void> {
285
+ async function shutdown(
286
+ exitCode: number,
287
+ restartReason?: string,
288
+ restartGroupContextKeys: string[] = [],
289
+ ): Promise<void> {
239
290
  if (shuttingDown) return;
240
291
  shuttingDown = true;
241
292
  logger.info({ exitCode }, "Shutting down Agent Bot.");
@@ -250,11 +301,11 @@ async function shutdown(exitCode: number, restartReason?: string): Promise<void>
250
301
  runtimes.close();
251
302
  await controlServer?.close().catch((error: unknown) => logger.warn({ error }, "Failed to close local control endpoint."));
252
303
  store.close();
253
- if (restartReason) startReplacementSupervisor(restartReason);
304
+ if (restartReason) startReplacementSupervisor(restartReason, restartGroupContextKeys);
254
305
  process.exit(exitCode);
255
306
  }
256
307
 
257
- function startReplacementSupervisor(restartReason: string): void {
308
+ function startReplacementSupervisor(restartReason: string, restartGroupContextKeys: string[]): void {
258
309
  const supervisorEntry = fileURLToPath(new URL("./supervisor.js", import.meta.url));
259
310
  const reportDirectory = resolveSupervisorDiagnosticsPaths(config).crashReportDirectory;
260
311
  prepareCrashReportDirectory(reportDirectory);
@@ -275,11 +326,100 @@ function startReplacementSupervisor(restartReason: string): void {
275
326
  detached: true,
276
327
  windowsHide: true,
277
328
  stdio: "ignore",
278
- env: replacementSupervisorEnvironment(restartReason, environmentRefresh.environment),
329
+ env: replacementSupervisorEnvironment(
330
+ restartReason,
331
+ environmentRefresh.environment,
332
+ restartGroupContextKeys,
333
+ ),
279
334
  });
280
335
  supervisor.unref();
281
336
  }
282
337
 
338
+ function inferControlRestartTarget(notificationSessionId?: string): RestartNotificationTarget | undefined {
339
+ const requestedSessionId = notificationSessionId?.trim();
340
+ if (requestedSessionId) {
341
+ const requested = store.getSession(requestedSessionId)
342
+ ?? store.findSessionByRemoteSessionId(requestedSessionId);
343
+ if (!requested) throw new Error(`Cannot route restart notifications: unknown task ${requestedSessionId}.`);
344
+ return restartNotificationTargetForSession(requested);
345
+ }
346
+
347
+ const runningSessions = store.listAllSessions().filter((session) => session.status === "running");
348
+ const runningContexts = new Set(runningSessions.map((session) => session.contextKey));
349
+ if (runningContexts.size > 1) {
350
+ throw new Error(
351
+ "Cannot determine which conversation requested the restart because multiple conversations are running. Pass --task <task>.",
352
+ );
353
+ }
354
+ const contextKey = runningContexts.size === 1 ? runningContexts.values().next().value : undefined;
355
+ const session = contextKey
356
+ ? runningSessions.find((candidate) => candidate.contextKey === contextKey)
357
+ : undefined;
358
+ return session ? restartNotificationTargetForSession(session) : undefined;
359
+ }
360
+
361
+ function restartNotificationTargetForSession(
362
+ session: { localSessionId: string; contextKey: string },
363
+ ): RestartNotificationTarget {
364
+ const replyMessageId = isThreadContextKey(session.contextKey)
365
+ ? store.findLatestMessageIdForSession(session.localSessionId, session.contextKey)
366
+ ?? store.findLatestMessageIdForContext(session.contextKey)
367
+ : undefined;
368
+ if (isThreadContextKey(session.contextKey) && !replyMessageId) {
369
+ throw new Error(
370
+ "Cannot route restart notifications to the task topic because it has no message anchor. Send /restart in that topic instead.",
371
+ );
372
+ }
373
+ return {
374
+ contextKey: session.contextKey,
375
+ ...(replyMessageId ? { replyMessageId } : {}),
376
+ };
377
+ }
378
+
379
+ async function sendRestartText(target: RestartNotificationTarget, text: string): Promise<string | undefined> {
380
+ if (isThreadContextKey(target.contextKey) && !target.replyMessageId) {
381
+ throw new Error("Cannot send a restart notice to a topic without a reply message anchor.");
382
+ }
383
+ const replyTarget = target.replyMessageId
384
+ ? { messageId: target.replyMessageId, replyInThread: true as const }
385
+ : undefined;
386
+ return outbound.withReplyTarget(target.contextKey, replyTarget, () =>
387
+ outbound.sendText(target.contextKey, text));
388
+ }
389
+
390
+ async function sendRestartTexts(targets: RestartNotificationTarget[], text: string): Promise<void> {
391
+ await Promise.all(targets.map(async (target) => {
392
+ await sendRestartText(target, text).catch((error: unknown) => {
393
+ logger.warn(
394
+ { error, contextKey: target.contextKey },
395
+ "Failed to send restart acknowledgement.",
396
+ );
397
+ });
398
+ }));
399
+ }
400
+
401
+ function groupContextKeysForRestartTargets(targets: RestartNotificationTarget[]): string[] {
402
+ const knownGroupContextKeys = new Set(
403
+ store.listChatContexts("group").map((chat) => chat.contextKey),
404
+ );
405
+ return [...new Set(targets
406
+ .map((target) => baseChatContextKey(target.contextKey))
407
+ .filter((contextKey) => knownGroupContextKeys.has(contextKey)))];
408
+ }
409
+
410
+ function mergeRestartNotificationTargets(
411
+ ...targetGroups: RestartNotificationTarget[][]
412
+ ): RestartNotificationTarget[] {
413
+ const targets = new Map<string, RestartNotificationTarget>();
414
+ for (const target of targetGroups.flat()) {
415
+ const existing = targets.get(target.contextKey);
416
+ if (!existing || (!existing.replyMessageId && target.replyMessageId)) {
417
+ targets.set(target.contextKey, { ...target });
418
+ }
419
+ }
420
+ return [...targets.values()];
421
+ }
422
+
283
423
  function delay(milliseconds: number): Promise<void> {
284
424
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
285
425
  }
@@ -0,0 +1,48 @@
1
+ export interface TurnGraphNode {
2
+ turnId: string;
3
+ parentTurnId?: string;
4
+ }
5
+
6
+ export interface TurnGraphRow {
7
+ sequence: number;
8
+ nodeLine: string;
9
+ connectorLine?: string;
10
+ }
11
+
12
+ export function buildTurnGraphRows(nodes: readonly TurnGraphNode[]): TurnGraphRow[] {
13
+ const visibleTurnIds = new Set(nodes.map((node) => node.turnId));
14
+ const lanes: string[] = [];
15
+
16
+ return nodes.map((node, index) => {
17
+ let lane = lanes.indexOf(node.turnId);
18
+ if (lane < 0) {
19
+ lane = lanes.length;
20
+ lanes.push(node.turnId);
21
+ }
22
+
23
+ const sequence = index + 1;
24
+ const nodeLine = `${lanes.map((_, laneIndex) => laneIndex === lane ? "●" : "│").join(" ")} ${sequence}`;
25
+ const parentTurnId = node.parentTurnId && visibleTurnIds.has(node.parentTurnId)
26
+ ? node.parentTurnId
27
+ : undefined;
28
+ let connectorLine: string | undefined;
29
+
30
+ if (!parentTurnId) {
31
+ lanes.splice(lane, 1);
32
+ connectorLine = lanes.length > 0 ? lanes.map(() => "│").join(" ") : undefined;
33
+ } else {
34
+ const existingParentLane = lanes.indexOf(parentTurnId);
35
+ if (existingParentLane < 0 || existingParentLane === lane) {
36
+ lanes[lane] = parentTurnId;
37
+ connectorLine = lanes.map(() => "│").join(" ");
38
+ } else {
39
+ const connectorLanes = lanes.map(() => "│");
40
+ connectorLanes[lane] = lane > existingParentLane ? "╱" : "╲";
41
+ connectorLine = connectorLanes.join(" ");
42
+ lanes.splice(lane, 1);
43
+ }
44
+ }
45
+
46
+ return { sequence, nodeLine, ...(connectorLine ? { connectorLine } : {}) };
47
+ });
48
+ }