@rk0429/agentic-relay 21.3.0 → 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 (105) 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 +4 -2
  26. package/dist/bin/relay.js +278 -11
  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.js +43 -6
  104. package/dist/interfaces/mcp/relay-mcp-server.js.map +1 -1
  105. 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,11 +50,13 @@ 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 の手順に従って実行してください。";
46
61
  export function buildRoutineStatusQueryService(options) {
47
62
  return new RoutineStatusQueryService({
@@ -52,6 +67,9 @@ export function buildRoutineStatusQueryService(options) {
52
67
  stateRepo: new RoutineStateRepository(options.store),
53
68
  runtimeRepo: new RoutineRuntimeProjectionRepository(options.store),
54
69
  store: options.store,
70
+ leaseRepo: new RoutineExecutionLeaseRepository(options.store),
71
+ loopStateFiles: () => options.store.listLoopStateFiles(),
72
+ deleteLoopState: (filename) => options.store.deleteLoopState(filename),
55
73
  });
56
74
  }
57
75
  async function main() {
@@ -184,9 +202,57 @@ async function main() {
184
202
  workflowRepo,
185
203
  configDir,
186
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
+ };
187
252
  if (command.kind === "routine-run" ||
188
253
  command.kind === "routine-start" ||
189
254
  command.kind === "routine-stop" ||
255
+ command.kind === "routine-cleanup" ||
190
256
  command.kind === "routine-list" ||
191
257
  command.kind === "routine-status") {
192
258
  const exitCode = await handleRoutineCommand(command, {
@@ -217,6 +283,26 @@ async function main() {
217
283
  }
218
284
  return;
219
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
+ }
220
306
  if (command.kind === "mcp") {
221
307
  const installed = await backendRegistry.detectInstalled();
222
308
  if (installed.length === 0) {
@@ -246,6 +332,7 @@ async function main() {
246
332
  routineStatusQueryService: buildRoutineStatusQueryService({
247
333
  store,
248
334
  workflowRepo,
335
+ configDir: command.configDir,
249
336
  }),
250
337
  allowSpawnAgents: process.env.RELAY_ALLOW_SPAWN_AGENTS !== "0",
251
338
  shutdownSignal: shutdownController.signal,
@@ -351,9 +438,9 @@ export async function handleRoutineCommand(command, dependencies) {
351
438
  const placeholderTime = new Date().toISOString();
352
439
  warnOnBuiltInRoutineVarOverrides(command.vars, dependencies.stderr);
353
440
  const routineVars = buildRoutineLoadVars(command.vars, placeholderTime);
354
- await dependencies.runStartupHousekeeping(dependencies.store, {
441
+ await suppressLoopStateWarnings(() => dependencies.runStartupHousekeeping(dependencies.store, {
355
442
  protectedLoopStateFile: command.resumeStateFile,
356
- });
443
+ }));
357
444
  await dependencies.reconcileAbandonedRunningTaskSessions(dependencies.store, dependencies.taskService);
358
445
  const loadedRoutine = await dependencies.loadRoutine(workflowPath, dependencies.workflowRepo, routineVars);
359
446
  warnOnBuiltInRoutineYamlVarOverrides(loadedRoutine.rawYamlVarKeys, dependencies.stderr);
@@ -375,7 +462,7 @@ export async function handleRoutineCommand(command, dependencies) {
375
462
  vars: command.vars ?? {},
376
463
  })
377
464
  : undefined;
378
- dependencies.stdout.write(renderRoutineDryRun(loadedRoutine, workflowPath, resumePreflight));
465
+ dependencies.stdout.write(renderRoutineDryRun(loadedRoutine, workflowPath, resumePreflight, command.vars, loadedRoutine.rawYamlVars));
379
466
  return 0;
380
467
  }
381
468
  enforceAllowCommandRun(workflowPath, loadedRoutine.capabilitySummary, command.allowCommandRun ?? false);
@@ -481,6 +568,26 @@ export async function handleRoutineCommand(command, dependencies) {
481
568
  await stopRoutineDaemon(dependencies);
482
569
  return 0;
483
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
+ }
484
591
  case "routine-list": {
485
592
  const service = dependencies.createRoutineStatusQueryService({
486
593
  configDir: resolveRoutineConfigDir(command.configDir, dependencies.store, dependencies.cwd),
@@ -501,11 +608,31 @@ export async function handleRoutineCommand(command, dependencies) {
501
608
  }
502
609
  }
503
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
+ }
504
628
  export function renderHelpText(topic, scriptName = path.basename(fileURLToPath(import.meta.url))) {
505
629
  if (topic) {
506
630
  if (isRoutineHelpTopic(topic)) {
507
631
  return renderRoutineHelp(topic, scriptName);
508
632
  }
633
+ if (isChatHelpTopic(topic)) {
634
+ return renderChatHelp(topic, scriptName);
635
+ }
509
636
  return renderCoreSubcommandHelp(topic, scriptName);
510
637
  }
511
638
  const commandName = path.parse(scriptName).name;
@@ -514,8 +641,13 @@ export function renderHelpText(topic, scriptName = path.basename(fileURLToPath(i
514
641
  ` ${scriptName} routine run <path> [--config <dir>] [--var key=value ...] [--dry-run] [--resume-state <file>] [--force-resume] [--allow-command-run]`,
515
642
  ` ${scriptName} routine start [--config <dir>] [--daemon] [--allow-command-run]`,
516
643
  ` ${scriptName} routine stop`,
644
+ ` ${scriptName} routine cleanup [--config <dir>] [--dry-run]`,
517
645
  ` ${scriptName} routine list [--config <dir>] [--format <table|tsv|json>]`,
518
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`,
519
651
  ` ${scriptName} core <setup|update|remove> [options]`,
520
652
  ` ${scriptName} setup-vscode [--target <code|code-insiders|cursor>] [--uninstall]`,
521
653
  ` ${scriptName} visualize`,
@@ -527,8 +659,13 @@ export function renderHelpText(topic, scriptName = path.basename(fileURLToPath(i
527
659
  ` ${commandName} routine run [options] Run one routine YAML immediately`,
528
660
  ` ${commandName} routine start [options] Start the routine daemon`,
529
661
  ` ${commandName} routine stop Stop the routine daemon`,
662
+ ` ${commandName} routine cleanup [options] Remove stale routine metadata`,
530
663
  ` ${commandName} routine list [options] List known routines`,
531
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`,
532
669
  ` ${commandName} core setup [options] Setup .agents/ from agentic-core dist`,
533
670
  ` ${commandName} core update [options] Update .agents/ from latest dist`,
534
671
  ` ${commandName} core remove [options] Remove relay core managed files`,
@@ -584,6 +721,82 @@ function renderCoreSubcommandHelp(topic, scriptName) {
584
721
  function isRoutineHelpTopic(topic) {
585
722
  return topic.startsWith("routine");
586
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
+ }
587
800
  function renderRoutineHelp(topic, scriptName) {
588
801
  if (topic === "routine") {
589
802
  return renderRoutineAggregateHelp(scriptName);
@@ -633,6 +846,19 @@ function renderRoutineHelp(topic, scriptName) {
633
846
  "",
634
847
  allSubcommandsReference,
635
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");
636
862
  case "routine-list":
637
863
  return [
638
864
  `Usage: ${scriptName} routine list [options]`,
@@ -655,7 +881,7 @@ function renderRoutineHelp(topic, scriptName) {
655
881
  "Options:",
656
882
  " --config <dir> Override the routine config directory",
657
883
  " --format <fmt> Output format (table, tsv, json)",
658
- " --limit <n> Limit the number of status log entries",
884
+ " --limit <n> Limit the number of status log entries (default: 10)",
659
885
  " -h, --help Show this help",
660
886
  "",
661
887
  allSubcommandsReference,
@@ -667,6 +893,7 @@ function renderRoutineAggregateHelp(scriptName) {
667
893
  `Usage: ${scriptName} routine run <workflow.yaml> [options]`,
668
894
  ` ${scriptName} routine start [options]`,
669
895
  ` ${scriptName} routine stop`,
896
+ ` ${scriptName} routine cleanup [options]`,
670
897
  ` ${scriptName} routine list [options]`,
671
898
  ` ${scriptName} routine status [name] [options]`,
672
899
  "",
@@ -674,6 +901,7 @@ function renderRoutineAggregateHelp(scriptName) {
674
901
  " run Run one routine YAML immediately",
675
902
  " start Start the routine daemon",
676
903
  " stop Stop the routine daemon",
904
+ " cleanup Remove stale routine metadata",
677
905
  " list List known routines",
678
906
  " status Show routine status details",
679
907
  "",
@@ -684,9 +912,9 @@ function renderRoutineAggregateHelp(scriptName) {
684
912
  " --allow-command-run Allow trusted workflows that declare command.run",
685
913
  " --resume-state <file> Resume a loop-capable routine from checkpoint state",
686
914
  " --force-resume Override workflow digest mismatch during resume",
687
- " --dry-run Render routine structure without executing it",
915
+ " --dry-run Render routine structure or cleanup targets without executing deletion",
688
916
  " --var key=value Set template variables for one routine run",
689
- " --limit <n> Limit the number of status log entries",
917
+ " --limit <n> Limit the number of status log entries (default: 10)",
690
918
  " -h, --help Show this help",
691
919
  ].join("\n");
692
920
  }
@@ -953,6 +1181,9 @@ function printRoutineRunResult(result, writer = process.stderr) {
953
1181
  writeLine(writer, lines.join("\n"));
954
1182
  }
955
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
+ }
956
1187
  function buildRoutineLoadVars(vars, placeholderValue) {
957
1188
  return {
958
1189
  ...(vars ?? {}),
@@ -1034,7 +1265,18 @@ async function validateRoutineDryRunResumeState(options) {
1034
1265
  });
1035
1266
  }
1036
1267
  }
1037
- 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.`);
1038
1280
  const lines = [
1039
1281
  `Routine: ${loadedRoutine.routineDefinition.name.value}`,
1040
1282
  ...(loadedRoutine.routineDefinition.description
@@ -1058,6 +1300,7 @@ function renderRoutineDryRun(loadedRoutine, workflowPath, resumePreflight) {
1058
1300
  : []),
1059
1301
  ]
1060
1302
  : []),
1303
+ ...(emptyVarWarnings.length > 0 ? ["Warnings:", ...emptyVarWarnings] : []),
1061
1304
  "",
1062
1305
  renderDryRun(loadedRoutine.routineDefinition.workflow, workflowPath),
1063
1306
  ];
@@ -1087,7 +1330,7 @@ function renderRoutineList(result, format = "table") {
1087
1330
  formatOptionalTimestamp(entry.nextScheduledAt),
1088
1331
  formatRoutineStatusWithOrphan(entry.status, entry.orphan),
1089
1332
  ].join("\t")),
1090
- ...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))}`),
1091
1334
  ];
1092
1335
  return `${lines.join("\n")}\n`;
1093
1336
  }
@@ -1117,10 +1360,14 @@ function renderRoutineList(result, format = "table") {
1117
1360
  }
1118
1361
  function renderInvalidRoutineFiles(invalid) {
1119
1362
  return [
1120
- `Skipped ${invalid.length} file(s):`,
1121
- ...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))}`),
1122
1365
  ].join("\n");
1123
1366
  }
1367
+ function stripYamlPathPrefix(yamlPath, error) {
1368
+ const prefix = `${yamlPath}: `;
1369
+ return error.startsWith(prefix) ? error.slice(prefix.length) : error;
1370
+ }
1124
1371
  function firstLine(value) {
1125
1372
  return value.split(/\r?\n/u, 1)[0] ?? "";
1126
1373
  }
@@ -1446,6 +1693,26 @@ function confirmFromStdin(message) {
1446
1693
  });
1447
1694
  });
1448
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
+ }
1449
1716
  async function isExecutedAsScript(metaUrl) {
1450
1717
  const entryPath = process.argv[1];
1451
1718
  if (!entryPath) {