@rk0429/agentic-relay 21.2.3 → 22.0.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 (106) hide show
  1. package/dist/application/chat/chat-daemon-service.d.ts +81 -0
  2. package/dist/application/chat/chat-daemon-service.js +382 -0
  3. package/dist/application/chat/chat-daemon-service.js.map +1 -0
  4. package/dist/application/chat/chat-inbound-handler.d.ts +35 -0
  5. package/dist/application/chat/chat-inbound-handler.js +175 -0
  6. package/dist/application/chat/chat-inbound-handler.js.map +1 -0
  7. package/dist/application/chat/chat-logging.d.ts +8 -0
  8. package/dist/application/chat/chat-logging.js +16 -0
  9. package/dist/application/chat/chat-logging.js.map +1 -0
  10. package/dist/application/chat/chat-outbound-handler.d.ts +16 -0
  11. package/dist/application/chat/chat-outbound-handler.js +190 -0
  12. package/dist/application/chat/chat-outbound-handler.js.map +1 -0
  13. package/dist/application/chat/chat-question-service.d.ts +37 -0
  14. package/dist/application/chat/chat-question-service.js +135 -0
  15. package/dist/application/chat/chat-question-service.js.map +1 -0
  16. package/dist/application/chat/chat-setup-service.d.ts +27 -0
  17. package/dist/application/chat/chat-setup-service.js +183 -0
  18. package/dist/application/chat/chat-setup-service.js.map +1 -0
  19. package/dist/application/chat/message-queue-manager.d.ts +13 -0
  20. package/dist/application/chat/message-queue-manager.js +34 -0
  21. package/dist/application/chat/message-queue-manager.js.map +1 -0
  22. package/dist/application/routine-status-query-service.d.ts +17 -0
  23. package/dist/application/routine-status-query-service.js +111 -0
  24. package/dist/application/routine-status-query-service.js.map +1 -1
  25. package/dist/bin/relay.d.ts +9 -2
  26. package/dist/bin/relay.js +296 -18
  27. package/dist/bin/relay.js.map +1 -1
  28. package/dist/core/chat-types.d.ts +18 -0
  29. package/dist/core/chat-types.js +14 -0
  30. package/dist/core/chat-types.js.map +1 -0
  31. package/dist/core/types.d.ts +7 -0
  32. package/dist/domain/chat/message-cleaner.d.ts +11 -0
  33. package/dist/domain/chat/message-cleaner.js +45 -0
  34. package/dist/domain/chat/message-cleaner.js.map +1 -0
  35. package/dist/domain/chat/message-router.d.ts +8 -0
  36. package/dist/domain/chat/message-router.js +93 -0
  37. package/dist/domain/chat/message-router.js.map +1 -0
  38. package/dist/domain/chat/message-splitter.d.ts +3 -0
  39. package/dist/domain/chat/message-splitter.js +148 -0
  40. package/dist/domain/chat/message-splitter.js.map +1 -0
  41. package/dist/domain/chat/platform-connection.d.ts +31 -0
  42. package/dist/domain/chat/platform-connection.js +67 -0
  43. package/dist/domain/chat/platform-connection.js.map +1 -0
  44. package/dist/domain/chat/ports.d.ts +31 -0
  45. package/dist/domain/chat/ports.js +2 -0
  46. package/dist/domain/chat/ports.js.map +1 -0
  47. package/dist/domain/chat/session-bridge.d.ts +41 -0
  48. package/dist/domain/chat/session-bridge.js +93 -0
  49. package/dist/domain/chat/session-bridge.js.map +1 -0
  50. package/dist/domain/chat/types.d.ts +83 -0
  51. package/dist/domain/chat/types.js +2 -0
  52. package/dist/domain/chat/types.js.map +1 -0
  53. package/dist/domain/chat/user-question.d.ts +33 -0
  54. package/dist/domain/chat/user-question.js +73 -0
  55. package/dist/domain/chat/user-question.js.map +1 -0
  56. package/dist/domain/config.d.ts +3 -1
  57. package/dist/domain/config.js +9 -0
  58. package/dist/domain/config.js.map +1 -1
  59. package/dist/domain/routine-loader.d.ts +2 -0
  60. package/dist/domain/routine-loader.js +3 -0
  61. package/dist/domain/routine-loader.js.map +1 -1
  62. package/dist/infrastructure/chat/adapter-factory.d.ts +5 -0
  63. package/dist/infrastructure/chat/adapter-factory.js +15 -0
  64. package/dist/infrastructure/chat/adapter-factory.js.map +1 -0
  65. package/dist/infrastructure/chat/adapters/discord-adapter.d.ts +23 -0
  66. package/dist/infrastructure/chat/adapters/discord-adapter.js +199 -0
  67. package/dist/infrastructure/chat/adapters/discord-adapter.js.map +1 -0
  68. package/dist/infrastructure/chat/adapters/slack-adapter.d.ts +24 -0
  69. package/dist/infrastructure/chat/adapters/slack-adapter.js +155 -0
  70. package/dist/infrastructure/chat/adapters/slack-adapter.js.map +1 -0
  71. package/dist/infrastructure/chat/agent-gateway-impl.d.ts +21 -0
  72. package/dist/infrastructure/chat/agent-gateway-impl.js +101 -0
  73. package/dist/infrastructure/chat/agent-gateway-impl.js.map +1 -0
  74. package/dist/infrastructure/chat/chat-ipc-client.d.ts +33 -0
  75. package/dist/infrastructure/chat/chat-ipc-client.js +70 -0
  76. package/dist/infrastructure/chat/chat-ipc-client.js.map +1 -0
  77. package/dist/infrastructure/chat/chat-ipc-server.d.ts +19 -0
  78. package/dist/infrastructure/chat/chat-ipc-server.js +125 -0
  79. package/dist/infrastructure/chat/chat-ipc-server.js.map +1 -0
  80. package/dist/infrastructure/chat/chat-logger.d.ts +24 -0
  81. package/dist/infrastructure/chat/chat-logger.js +86 -0
  82. package/dist/infrastructure/chat/chat-logger.js.map +1 -0
  83. package/dist/infrastructure/chat/credentials-repository.d.ts +8 -0
  84. package/dist/infrastructure/chat/credentials-repository.js +84 -0
  85. package/dist/infrastructure/chat/credentials-repository.js.map +1 -0
  86. package/dist/infrastructure/chat/session-bridge-repository.d.ts +13 -0
  87. package/dist/infrastructure/chat/session-bridge-repository.js +120 -0
  88. package/dist/infrastructure/chat/session-bridge-repository.js.map +1 -0
  89. package/dist/infrastructure/chat/token-masking.d.ts +3 -0
  90. package/dist/infrastructure/chat/token-masking.js +22 -0
  91. package/dist/infrastructure/chat/token-masking.js.map +1 -0
  92. package/dist/infrastructure/store/relay-store.js +3 -1
  93. package/dist/infrastructure/store/relay-store.js.map +1 -1
  94. package/dist/interfaces/cli/chat-cli.d.ts +34 -0
  95. package/dist/interfaces/cli/chat-cli.js +68 -0
  96. package/dist/interfaces/cli/chat-cli.js.map +1 -0
  97. package/dist/interfaces/cli/relay-cli-args.d.ts +16 -1
  98. package/dist/interfaces/cli/relay-cli-args.js +135 -2
  99. package/dist/interfaces/cli/relay-cli-args.js.map +1 -1
  100. package/dist/interfaces/mcp/chat-tools.d.ts +33 -0
  101. package/dist/interfaces/mcp/chat-tools.js +129 -0
  102. package/dist/interfaces/mcp/chat-tools.js.map +1 -0
  103. package/dist/interfaces/mcp/relay-mcp-server.d.ts +2 -0
  104. package/dist/interfaces/mcp/relay-mcp-server.js +194 -1
  105. package/dist/interfaces/mcp/relay-mcp-server.js.map +1 -1
  106. package/package.json +3 -1
package/dist/bin/relay.js CHANGED
@@ -1,11 +1,18 @@
1
1
  #!/usr/bin/env -S node --disable-warning=ExperimentalWarning
2
- import { fork } from "node:child_process";
2
+ import { execFile, fork } from "node:child_process";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { existsSync } from "node:fs";
5
5
  import { access, readFile, realpath, rm } from "node:fs/promises";
6
6
  import path from "node:path";
7
7
  import { createInterface } from "node:readline";
8
8
  import { fileURLToPath } from "node:url";
9
+ import { promisify } from "node:util";
10
+ import { ChatDaemonService, } from "../application/chat/chat-daemon-service.js";
11
+ import { ChatInboundHandler } from "../application/chat/chat-inbound-handler.js";
12
+ import { ChatOutboundHandler } from "../application/chat/chat-outbound-handler.js";
13
+ import { ChatQuestionService, InMemoryUserQuestionRepository, } from "../application/chat/chat-question-service.js";
14
+ import { ChatSetupService } from "../application/chat/chat-setup-service.js";
15
+ import { MessageQueueManager } from "../application/chat/message-queue-manager.js";
9
16
  import { CoreManagementService, } from "../application/core-management-service.js";
10
17
  import { LoopEventLogSink } from "../application/event-log-sink.js";
11
18
  import { isProcessAlive, reconcileAbandonedRunningTaskSessions, runStartupHousekeeping, } from "../application/housekeeping.js";
@@ -28,6 +35,12 @@ import { resolveRole } from "../domain/role.js";
28
35
  import { resolveBackend, validateRoutingRules } from "../domain/routing.js";
29
36
  import { loadRoutine } from "../domain/routine-loader.js";
30
37
  import { BackendRegistry } from "../infrastructure/backends/backend-registry.js";
38
+ import { AdapterFactory } from "../infrastructure/chat/adapter-factory.js";
39
+ import { AgentGatewayImpl } from "../infrastructure/chat/agent-gateway-impl.js";
40
+ import { ChatIpcServer } from "../infrastructure/chat/chat-ipc-server.js";
41
+ import { ChatLogger } from "../infrastructure/chat/chat-logger.js";
42
+ import { CredentialsFileRepository } from "../infrastructure/chat/credentials-repository.js";
43
+ import { SessionBridgeFileRepository } from "../infrastructure/chat/session-bridge-repository.js";
31
44
  import { CommandExecutionGateway } from "../infrastructure/command-execution-gateway.js";
32
45
  import { CliBackendExecutor } from "../infrastructure/backends/cli-backend-executor.js";
33
46
  import { ChildProcessExecutor, ChildProcessInteractiveRunner, } from "../infrastructure/process/process-executor.js";
@@ -37,12 +50,28 @@ import { isMissingFileError } from "../infrastructure/store/file-utils.js";
37
50
  import { RoutineExecutionLeaseRepository } from "../infrastructure/store/routine-execution-lease-repository.js";
38
51
  import { RoutineRuntimeProjectionRepository } from "../infrastructure/store/routine-runtime-projection-repository.js";
39
52
  import { RoutineStateRepository } from "../infrastructure/store/routine-state-repository.js";
53
+ import { handleChatCommand, } from "../interfaces/cli/chat-cli.js";
40
54
  import { parseRelayCliArgs, } from "../interfaces/cli/relay-cli-args.js";
41
55
  import { createLoopProgressReporter } from "../interfaces/cli/loop-progress-shell.js";
42
56
  import { runRelayInteractiveShell } from "../interfaces/cli/relay-shell.js";
43
57
  import { renderVisualization } from "../interfaces/cli/visualization-renderer.js";
44
58
  import { createRelayMcpServer, serveRelayMcpServer, } from "../interfaces/mcp/relay-mcp-server.js";
59
+ const execFileAsync = promisify(execFile);
45
60
  export const AGENTIC_SETUP_PROMPT = "ワークスペースのエージェント基盤をセットアップしてください。 .agents/skills/agentic-setup/SKILL.md の手順に従って実行してください。";
61
+ export function buildRoutineStatusQueryService(options) {
62
+ return new RoutineStatusQueryService({
63
+ catalogService: new RoutineCatalogService({
64
+ configDir: options.configDir ?? options.store.routinesDir(),
65
+ workflowRepo: options.workflowRepo,
66
+ }),
67
+ stateRepo: new RoutineStateRepository(options.store),
68
+ runtimeRepo: new RoutineRuntimeProjectionRepository(options.store),
69
+ store: options.store,
70
+ leaseRepo: new RoutineExecutionLeaseRepository(options.store),
71
+ loopStateFiles: () => options.store.listLoopStateFiles(),
72
+ deleteLoopState: (filename) => options.store.deleteLoopState(filename),
73
+ });
74
+ }
46
75
  async function main() {
47
76
  const command = parseRelayCliArgs(process.argv.slice(2));
48
77
  const cwd = process.cwd();
@@ -168,18 +197,62 @@ async function main() {
168
197
  onRoutineEvent,
169
198
  });
170
199
  };
171
- const createRoutineStatusQueryService = ({ configDir }) => new RoutineStatusQueryService({
172
- catalogService: new RoutineCatalogService({
173
- configDir,
174
- workflowRepo,
175
- }),
176
- stateRepo: new RoutineStateRepository(store),
177
- runtimeRepo: new RoutineRuntimeProjectionRepository(store),
200
+ const createRoutineStatusQueryService = ({ configDir }) => buildRoutineStatusQueryService({
178
201
  store,
202
+ workflowRepo,
203
+ configDir,
179
204
  });
205
+ const createChatSetupService = () => new ChatSetupService({
206
+ credentialsRepository: new CredentialsFileRepository(store.rootDir),
207
+ prompt: promptFromStdin,
208
+ confirm: confirmFromStdin,
209
+ openUrl: openUrlInBrowser,
210
+ logger: (message) => writeLine(process.stderr, message),
211
+ });
212
+ const createChatDaemonService = () => {
213
+ const credentialsRepository = new CredentialsFileRepository(store.rootDir);
214
+ const sessionBridgeRepository = new SessionBridgeFileRepository(store.rootDir);
215
+ const outboundHandler = new ChatOutboundHandler();
216
+ const questionService = new ChatQuestionService({
217
+ userQuestionRepository: new InMemoryUserQuestionRepository(),
218
+ });
219
+ const spawnService = new SpawnAgentsService({
220
+ backendRegistry,
221
+ backendExecutor,
222
+ store,
223
+ taskService,
224
+ cwd,
225
+ env: process.env,
226
+ });
227
+ const inboundHandler = new ChatInboundHandler({
228
+ agentGateway: new AgentGatewayImpl(spawnService, taskService, store, backendRegistry),
229
+ sessionBridgeRepository,
230
+ outboundHandler,
231
+ questionService,
232
+ messageQueueManager: new MessageQueueManager(),
233
+ logger: (message) => writeLine(process.stderr, message),
234
+ });
235
+ const ipcServer = new ChatIpcServer({
236
+ rootDir: store.rootDir,
237
+ outboundHandler,
238
+ questionService,
239
+ });
240
+ return new ChatDaemonService({
241
+ store,
242
+ credentialsRepository,
243
+ sessionBridgeRepository,
244
+ adapterFactory: new AdapterFactory(),
245
+ outboundHandler,
246
+ questionService,
247
+ inboundHandler,
248
+ ipcServer,
249
+ createLogger: (options) => new ChatLogger(options),
250
+ });
251
+ };
180
252
  if (command.kind === "routine-run" ||
181
253
  command.kind === "routine-start" ||
182
254
  command.kind === "routine-stop" ||
255
+ command.kind === "routine-cleanup" ||
183
256
  command.kind === "routine-list" ||
184
257
  command.kind === "routine-status") {
185
258
  const exitCode = await handleRoutineCommand(command, {
@@ -210,6 +283,26 @@ async function main() {
210
283
  }
211
284
  return;
212
285
  }
286
+ if (command.kind === "chat-setup" ||
287
+ command.kind === "chat-start" ||
288
+ command.kind === "chat-stop" ||
289
+ command.kind === "chat-status") {
290
+ const exitCode = await handleChatCommand(command, {
291
+ cwd,
292
+ argv: process.argv.slice(2),
293
+ scriptPath: process.argv[1] ?? fileURLToPath(import.meta.url),
294
+ env: process.env,
295
+ stdout: process.stdout,
296
+ stderr: process.stderr,
297
+ createSetupService: createChatSetupService,
298
+ createDaemonService: createChatDaemonService,
299
+ forkProcess: (modulePath, args, options) => fork(modulePath, args, options),
300
+ });
301
+ if (exitCode !== undefined) {
302
+ process.exitCode = exitCode;
303
+ }
304
+ return;
305
+ }
213
306
  if (command.kind === "mcp") {
214
307
  const installed = await backendRegistry.detectInstalled();
215
308
  if (installed.length === 0) {
@@ -236,6 +329,11 @@ async function main() {
236
329
  service,
237
330
  taskService,
238
331
  store,
332
+ routineStatusQueryService: buildRoutineStatusQueryService({
333
+ store,
334
+ workflowRepo,
335
+ configDir: command.configDir,
336
+ }),
239
337
  allowSpawnAgents: process.env.RELAY_ALLOW_SPAWN_AGENTS !== "0",
240
338
  shutdownSignal: shutdownController.signal,
241
339
  });
@@ -340,9 +438,9 @@ export async function handleRoutineCommand(command, dependencies) {
340
438
  const placeholderTime = new Date().toISOString();
341
439
  warnOnBuiltInRoutineVarOverrides(command.vars, dependencies.stderr);
342
440
  const routineVars = buildRoutineLoadVars(command.vars, placeholderTime);
343
- await dependencies.runStartupHousekeeping(dependencies.store, {
441
+ await suppressLoopStateWarnings(() => dependencies.runStartupHousekeeping(dependencies.store, {
344
442
  protectedLoopStateFile: command.resumeStateFile,
345
- });
443
+ }));
346
444
  await dependencies.reconcileAbandonedRunningTaskSessions(dependencies.store, dependencies.taskService);
347
445
  const loadedRoutine = await dependencies.loadRoutine(workflowPath, dependencies.workflowRepo, routineVars);
348
446
  warnOnBuiltInRoutineYamlVarOverrides(loadedRoutine.rawYamlVarKeys, dependencies.stderr);
@@ -364,7 +462,7 @@ export async function handleRoutineCommand(command, dependencies) {
364
462
  vars: command.vars ?? {},
365
463
  })
366
464
  : undefined;
367
- dependencies.stdout.write(renderRoutineDryRun(loadedRoutine, workflowPath, resumePreflight));
465
+ dependencies.stdout.write(renderRoutineDryRun(loadedRoutine, workflowPath, resumePreflight, command.vars, loadedRoutine.rawYamlVars));
368
466
  return 0;
369
467
  }
370
468
  enforceAllowCommandRun(workflowPath, loadedRoutine.capabilitySummary, command.allowCommandRun ?? false);
@@ -470,6 +568,26 @@ export async function handleRoutineCommand(command, dependencies) {
470
568
  await stopRoutineDaemon(dependencies);
471
569
  return 0;
472
570
  }
571
+ case "routine-cleanup": {
572
+ const service = dependencies.createRoutineStatusQueryService({
573
+ configDir: resolveRoutineConfigDir(command.configDir, dependencies.store, dependencies.cwd),
574
+ });
575
+ const result = await service.cleanup(command.dryRun ?? false);
576
+ if (result.targets.length === 0) {
577
+ dependencies.stderr.write("No cleanup targets found.\n");
578
+ return 0;
579
+ }
580
+ if (command.dryRun) {
581
+ dependencies.stderr.write("Cleanup targets (dry-run):\n");
582
+ }
583
+ else {
584
+ dependencies.stderr.write(`Cleaned up ${result.deleted} file(s):\n`);
585
+ }
586
+ for (const target of result.targets) {
587
+ dependencies.stderr.write(` ${target.kind}: ${target.filePath} — ${target.reason}\n`);
588
+ }
589
+ return 0;
590
+ }
473
591
  case "routine-list": {
474
592
  const service = dependencies.createRoutineStatusQueryService({
475
593
  configDir: resolveRoutineConfigDir(command.configDir, dependencies.store, dependencies.cwd),
@@ -490,11 +608,31 @@ export async function handleRoutineCommand(command, dependencies) {
490
608
  }
491
609
  }
492
610
  }
611
+ async function suppressLoopStateWarnings(fn) {
612
+ const origWarn = console.warn;
613
+ console.warn = (...args) => {
614
+ const msg = typeof args[0] === "string" ? args[0] : "";
615
+ if (msg.startsWith("Ignoring invalid loop state file")) {
616
+ console.debug(...args);
617
+ return;
618
+ }
619
+ origWarn.apply(console, args);
620
+ };
621
+ try {
622
+ return await fn();
623
+ }
624
+ finally {
625
+ console.warn = origWarn;
626
+ }
627
+ }
493
628
  export function renderHelpText(topic, scriptName = path.basename(fileURLToPath(import.meta.url))) {
494
629
  if (topic) {
495
630
  if (isRoutineHelpTopic(topic)) {
496
631
  return renderRoutineHelp(topic, scriptName);
497
632
  }
633
+ if (isChatHelpTopic(topic)) {
634
+ return renderChatHelp(topic, scriptName);
635
+ }
498
636
  return renderCoreSubcommandHelp(topic, scriptName);
499
637
  }
500
638
  const commandName = path.parse(scriptName).name;
@@ -503,8 +641,13 @@ export function renderHelpText(topic, scriptName = path.basename(fileURLToPath(i
503
641
  ` ${scriptName} routine run <path> [--config <dir>] [--var key=value ...] [--dry-run] [--resume-state <file>] [--force-resume] [--allow-command-run]`,
504
642
  ` ${scriptName} routine start [--config <dir>] [--daemon] [--allow-command-run]`,
505
643
  ` ${scriptName} routine stop`,
644
+ ` ${scriptName} routine cleanup [--config <dir>] [--dry-run]`,
506
645
  ` ${scriptName} routine list [--config <dir>] [--format <table|tsv|json>]`,
507
646
  ` ${scriptName} routine status [name] [--config <dir>] [--format <table|tsv|json>] [--limit <n>]`,
647
+ ` ${scriptName} chat setup (--slack | --discord)`,
648
+ ` ${scriptName} chat start [--foreground]`,
649
+ ` ${scriptName} chat stop`,
650
+ ` ${scriptName} chat status`,
508
651
  ` ${scriptName} core <setup|update|remove> [options]`,
509
652
  ` ${scriptName} setup-vscode [--target <code|code-insiders|cursor>] [--uninstall]`,
510
653
  ` ${scriptName} visualize`,
@@ -516,8 +659,13 @@ export function renderHelpText(topic, scriptName = path.basename(fileURLToPath(i
516
659
  ` ${commandName} routine run [options] Run one routine YAML immediately`,
517
660
  ` ${commandName} routine start [options] Start the routine daemon`,
518
661
  ` ${commandName} routine stop Stop the routine daemon`,
662
+ ` ${commandName} routine cleanup [options] Remove stale routine metadata`,
519
663
  ` ${commandName} routine list [options] List known routines`,
520
664
  ` ${commandName} routine status [options] Show routine status details`,
665
+ ` ${commandName} chat setup [options] Configure Slack or Discord integration`,
666
+ ` ${commandName} chat start [options] Start the chat daemon`,
667
+ ` ${commandName} chat stop Stop the chat daemon`,
668
+ ` ${commandName} chat status Show chat daemon status`,
521
669
  ` ${commandName} core setup [options] Setup .agents/ from agentic-core dist`,
522
670
  ` ${commandName} core update [options] Update .agents/ from latest dist`,
523
671
  ` ${commandName} core remove [options] Remove relay core managed files`,
@@ -573,6 +721,82 @@ function renderCoreSubcommandHelp(topic, scriptName) {
573
721
  function isRoutineHelpTopic(topic) {
574
722
  return topic.startsWith("routine");
575
723
  }
724
+ function isChatHelpTopic(topic) {
725
+ return topic.startsWith("chat");
726
+ }
727
+ function renderChatHelp(topic, scriptName) {
728
+ if (topic === "chat") {
729
+ return [
730
+ `Usage: ${scriptName} chat setup (--slack | --discord)`,
731
+ ` ${scriptName} chat start [--foreground]`,
732
+ ` ${scriptName} chat stop`,
733
+ ` ${scriptName} chat status`,
734
+ "",
735
+ "Chat commands:",
736
+ " setup Configure Slack or Discord credentials",
737
+ " start Start the chat daemon",
738
+ " stop Stop the chat daemon",
739
+ " status Show chat daemon status",
740
+ "",
741
+ "Options:",
742
+ " --slack Setup Slack integration",
743
+ " --discord Setup Discord integration",
744
+ " --foreground Run the chat daemon in the foreground",
745
+ " -h, --help Show this help",
746
+ ].join("\n");
747
+ }
748
+ const commandName = path.parse(scriptName).name;
749
+ const allSubcommandsReference = `See '${commandName} chat --help' for all subcommands.`;
750
+ switch (topic) {
751
+ case "chat-setup":
752
+ return [
753
+ `Usage: ${scriptName} chat setup (--slack | --discord)`,
754
+ "",
755
+ "Configure Slack or Discord credentials for the chat daemon.",
756
+ "",
757
+ "Options:",
758
+ " --slack Configure Slack",
759
+ " --discord Configure Discord",
760
+ " -h, --help Show this help",
761
+ "",
762
+ allSubcommandsReference,
763
+ ].join("\n");
764
+ case "chat-start":
765
+ return [
766
+ `Usage: ${scriptName} chat start [--foreground]`,
767
+ "",
768
+ "Start the chat daemon.",
769
+ "",
770
+ "Options:",
771
+ " --foreground Run in the foreground for debugging",
772
+ " -h, --help Show this help",
773
+ "",
774
+ allSubcommandsReference,
775
+ ].join("\n");
776
+ case "chat-stop":
777
+ return [
778
+ `Usage: ${scriptName} chat stop`,
779
+ "",
780
+ "Stop the chat daemon.",
781
+ "",
782
+ "Options:",
783
+ " -h, --help Show this help",
784
+ "",
785
+ allSubcommandsReference,
786
+ ].join("\n");
787
+ case "chat-status":
788
+ return [
789
+ `Usage: ${scriptName} chat status`,
790
+ "",
791
+ "Show chat daemon status.",
792
+ "",
793
+ "Options:",
794
+ " -h, --help Show this help",
795
+ "",
796
+ allSubcommandsReference,
797
+ ].join("\n");
798
+ }
799
+ }
576
800
  function renderRoutineHelp(topic, scriptName) {
577
801
  if (topic === "routine") {
578
802
  return renderRoutineAggregateHelp(scriptName);
@@ -622,6 +846,19 @@ function renderRoutineHelp(topic, scriptName) {
622
846
  "",
623
847
  allSubcommandsReference,
624
848
  ].join("\n");
849
+ case "routine-cleanup":
850
+ return [
851
+ `Usage: ${scriptName} routine cleanup [options]`,
852
+ "",
853
+ "Remove stale routine metadata files.",
854
+ "",
855
+ "Options:",
856
+ " --config <dir> Override the routine config directory",
857
+ " --dry-run Show cleanup targets without deleting them",
858
+ " -h, --help Show this help",
859
+ "",
860
+ allSubcommandsReference,
861
+ ].join("\n");
625
862
  case "routine-list":
626
863
  return [
627
864
  `Usage: ${scriptName} routine list [options]`,
@@ -644,7 +881,7 @@ function renderRoutineHelp(topic, scriptName) {
644
881
  "Options:",
645
882
  " --config <dir> Override the routine config directory",
646
883
  " --format <fmt> Output format (table, tsv, json)",
647
- " --limit <n> Limit the number of status log entries",
884
+ " --limit <n> Limit the number of status log entries (default: 10)",
648
885
  " -h, --help Show this help",
649
886
  "",
650
887
  allSubcommandsReference,
@@ -656,6 +893,7 @@ function renderRoutineAggregateHelp(scriptName) {
656
893
  `Usage: ${scriptName} routine run <workflow.yaml> [options]`,
657
894
  ` ${scriptName} routine start [options]`,
658
895
  ` ${scriptName} routine stop`,
896
+ ` ${scriptName} routine cleanup [options]`,
659
897
  ` ${scriptName} routine list [options]`,
660
898
  ` ${scriptName} routine status [name] [options]`,
661
899
  "",
@@ -663,6 +901,7 @@ function renderRoutineAggregateHelp(scriptName) {
663
901
  " run Run one routine YAML immediately",
664
902
  " start Start the routine daemon",
665
903
  " stop Stop the routine daemon",
904
+ " cleanup Remove stale routine metadata",
666
905
  " list List known routines",
667
906
  " status Show routine status details",
668
907
  "",
@@ -673,9 +912,9 @@ function renderRoutineAggregateHelp(scriptName) {
673
912
  " --allow-command-run Allow trusted workflows that declare command.run",
674
913
  " --resume-state <file> Resume a loop-capable routine from checkpoint state",
675
914
  " --force-resume Override workflow digest mismatch during resume",
676
- " --dry-run Render routine structure without executing it",
915
+ " --dry-run Render routine structure or cleanup targets without executing deletion",
677
916
  " --var key=value Set template variables for one routine run",
678
- " --limit <n> Limit the number of status log entries",
917
+ " --limit <n> Limit the number of status log entries (default: 10)",
679
918
  " -h, --help Show this help",
680
919
  ].join("\n");
681
920
  }
@@ -942,6 +1181,9 @@ function printRoutineRunResult(result, writer = process.stderr) {
942
1181
  writeLine(writer, lines.join("\n"));
943
1182
  }
944
1183
  const BUILT_IN_ROUTINE_VAR_KEYS = ["scheduled_time", "trigger_time"];
1184
+ function isBuiltInRoutineVarKey(key) {
1185
+ return BUILT_IN_ROUTINE_VAR_KEYS.includes(key);
1186
+ }
945
1187
  function buildRoutineLoadVars(vars, placeholderValue) {
946
1188
  return {
947
1189
  ...(vars ?? {}),
@@ -1023,7 +1265,18 @@ async function validateRoutineDryRunResumeState(options) {
1023
1265
  });
1024
1266
  }
1025
1267
  }
1026
- function renderRoutineDryRun(loadedRoutine, workflowPath, resumePreflight) {
1268
+ function renderRoutineDryRun(loadedRoutine, workflowPath, resumePreflight, vars, yamlVars) {
1269
+ const cliVarKeys = new Set(Object.keys(vars ?? {}));
1270
+ const emptyVarWarnings = [
1271
+ ...Object.entries(vars ?? {})
1272
+ .filter(([key, value]) => value === "" && !isBuiltInRoutineVarKey(key))
1273
+ .map(([key]) => key),
1274
+ ...Object.entries(yamlVars ?? {})
1275
+ .filter(([key, value]) => value === "" &&
1276
+ !isBuiltInRoutineVarKey(key) &&
1277
+ !cliVarKeys.has(key))
1278
+ .map(([key]) => key),
1279
+ ].map((key) => ` - Template variable "{{${key}}}" resolved to empty string.`);
1027
1280
  const lines = [
1028
1281
  `Routine: ${loadedRoutine.routineDefinition.name.value}`,
1029
1282
  ...(loadedRoutine.routineDefinition.description
@@ -1047,6 +1300,7 @@ function renderRoutineDryRun(loadedRoutine, workflowPath, resumePreflight) {
1047
1300
  : []),
1048
1301
  ]
1049
1302
  : []),
1303
+ ...(emptyVarWarnings.length > 0 ? ["Warnings:", ...emptyVarWarnings] : []),
1050
1304
  "",
1051
1305
  renderDryRun(loadedRoutine.routineDefinition.workflow, workflowPath),
1052
1306
  ];
@@ -1076,7 +1330,7 @@ function renderRoutineList(result, format = "table") {
1076
1330
  formatOptionalTimestamp(entry.nextScheduledAt),
1077
1331
  formatRoutineStatusWithOrphan(entry.status, entry.orphan),
1078
1332
  ].join("\t")),
1079
- ...invalid.map((entry) => `# Skipped: ${path.basename(entry.yamlPath)}: ${entry.error.replace(/\r?\n/g, " | ")}`),
1333
+ ...invalid.map((entry) => `# ${path.basename(entry.yamlPath)}: ${firstLine(stripYamlPathPrefix(entry.yamlPath, entry.error))}`),
1080
1334
  ];
1081
1335
  return `${lines.join("\n")}\n`;
1082
1336
  }
@@ -1106,10 +1360,14 @@ function renderRoutineList(result, format = "table") {
1106
1360
  }
1107
1361
  function renderInvalidRoutineFiles(invalid) {
1108
1362
  return [
1109
- `Skipped ${invalid.length} file(s):`,
1110
- ...invalid.map((entry) => ` - ${path.basename(entry.yamlPath)}: ${firstLine(entry.error)}`),
1363
+ `Skipped (${invalid.length}):`,
1364
+ ...invalid.map((entry) => ` - ${path.basename(entry.yamlPath)}: ${firstLine(stripYamlPathPrefix(entry.yamlPath, entry.error))}`),
1111
1365
  ].join("\n");
1112
1366
  }
1367
+ function stripYamlPathPrefix(yamlPath, error) {
1368
+ const prefix = `${yamlPath}: `;
1369
+ return error.startsWith(prefix) ? error.slice(prefix.length) : error;
1370
+ }
1113
1371
  function firstLine(value) {
1114
1372
  return value.split(/\r?\n/u, 1)[0] ?? "";
1115
1373
  }
@@ -1435,6 +1693,26 @@ function confirmFromStdin(message) {
1435
1693
  });
1436
1694
  });
1437
1695
  }
1696
+ function promptFromStdin(message) {
1697
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
1698
+ return new Promise((resolve) => {
1699
+ rl.question(message, (answer) => {
1700
+ rl.close();
1701
+ resolve(answer);
1702
+ });
1703
+ });
1704
+ }
1705
+ async function openUrlInBrowser(url) {
1706
+ if (process.platform === "darwin") {
1707
+ await execFileAsync("open", [url]);
1708
+ return;
1709
+ }
1710
+ if (process.platform === "win32") {
1711
+ await execFileAsync("cmd", ["/c", "start", "", url]);
1712
+ return;
1713
+ }
1714
+ await execFileAsync("xdg-open", [url]);
1715
+ }
1438
1716
  async function isExecutedAsScript(metaUrl) {
1439
1717
  const entryPath = process.argv[1];
1440
1718
  if (!entryPath) {