@clinebot/core 0.0.12 → 0.0.13

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.
@@ -0,0 +1,11 @@
1
+ import type { UserInstructionConfigWatcher } from "../agents";
2
+ export type RuntimeCommandKind = "skill" | "workflow";
3
+ export type AvailableRuntimeCommand = {
4
+ id: string;
5
+ name: string;
6
+ instructions: string;
7
+ kind: RuntimeCommandKind;
8
+ };
9
+ export declare function listAvailableRuntimeCommandsFromWatcher(watcher: UserInstructionConfigWatcher): AvailableRuntimeCommand[];
10
+ export declare function resolveRuntimeSlashCommandFromWatcher(input: string, watcher: UserInstructionConfigWatcher): string;
11
+ export declare function listAvailableRuntimeCommandsForKindFromWatcher(watcher: UserInstructionConfigWatcher, kind: RuntimeCommandKind): AvailableRuntimeCommand[];
@@ -0,0 +1,13 @@
1
+ import type { UserInstructionConfigWatcher } from "../agents";
2
+ export type AvailableSkill = {
3
+ id: string;
4
+ name: string;
5
+ instructions: string;
6
+ };
7
+ export declare function listAvailableSkillsFromWatcher(watcher: UserInstructionConfigWatcher): AvailableSkill[];
8
+ /**
9
+ * Expands a leading slash command (e.g. "/release") to skill instructions.
10
+ * If the input starts with "/<skill-name>", that prefix is replaced and the
11
+ * remaining input is preserved unchanged.
12
+ */
13
+ export declare function resolveSkillsSlashCommandFromWatcher(input: string, watcher: UserInstructionConfigWatcher): string;
@@ -3,35 +3,35 @@ import type { SqliteSessionStore } from "../storage/sqlite-session-store";
3
3
  import type { SessionSource, SessionStatus } from "../types/common";
4
4
  import type { SessionManifest } from "./session-manifest";
5
5
  import { UnifiedSessionPersistenceService } from "./unified-session-persistence-service";
6
- export interface SessionRowShape {
7
- session_id: string;
6
+ export interface SessionRow {
7
+ sessionId: string;
8
8
  source: string;
9
9
  pid: number;
10
- started_at: string;
11
- ended_at?: string | null;
12
- exit_code?: number | null;
10
+ startedAt: string;
11
+ endedAt?: string | null;
12
+ exitCode?: number | null;
13
13
  status: SessionStatus;
14
- status_lock?: number;
15
- interactive: number;
14
+ statusLock: number;
15
+ interactive: boolean;
16
16
  provider: string;
17
17
  model: string;
18
18
  cwd: string;
19
- workspace_root: string;
20
- team_name?: string | null;
21
- enable_tools: number;
22
- enable_spawn: number;
23
- enable_teams: number;
24
- parent_session_id?: string | null;
25
- parent_agent_id?: string | null;
26
- agent_id?: string | null;
27
- conversation_id?: string | null;
28
- is_subagent: number;
19
+ workspaceRoot: string;
20
+ teamName?: string | null;
21
+ enableTools: boolean;
22
+ enableSpawn: boolean;
23
+ enableTeams: boolean;
24
+ parentSessionId?: string | null;
25
+ parentAgentId?: string | null;
26
+ agentId?: string | null;
27
+ conversationId?: string | null;
28
+ isSubagent: boolean;
29
29
  prompt?: string | null;
30
- metadata_json?: string | null;
31
- transcript_path: string;
32
- hook_path: string;
33
- messages_path?: string | null;
34
- updated_at?: string;
30
+ metadata?: Record<string, unknown> | null;
31
+ transcriptPath: string;
32
+ hookPath: string;
33
+ messagesPath?: string | null;
34
+ updatedAt: string;
35
35
  }
36
36
  export interface CreateRootSessionInput {
37
37
  sessionId: string;
@@ -3,7 +3,7 @@ import type { LlmsProviders } from "@clinebot/llms";
3
3
  import type { SessionStatus } from "../types/common";
4
4
  import { SessionArtifacts } from "./session-artifacts";
5
5
  import { type SessionManifest } from "./session-manifest";
6
- import type { CreateRootSessionWithArtifactsInput, RootSessionArtifacts, SessionRowShape, UpsertSubagentInput } from "./session-service";
6
+ import type { CreateRootSessionWithArtifactsInput, RootSessionArtifacts, SessionRow, UpsertSubagentInput } from "./session-service";
7
7
  export interface PersistedSessionUpdateInput {
8
8
  sessionId: string;
9
9
  expectedStatusLock?: number;
@@ -11,7 +11,7 @@ export interface PersistedSessionUpdateInput {
11
11
  endedAt?: string | null;
12
12
  exitCode?: number | null;
13
13
  prompt?: string | null;
14
- metadataJson?: string | null;
14
+ metadata?: Record<string, unknown> | null;
15
15
  title?: string | null;
16
16
  parentSessionId?: string | null;
17
17
  parentAgentId?: string | null;
@@ -21,13 +21,13 @@ export interface PersistedSessionUpdateInput {
21
21
  }
22
22
  export interface SessionPersistenceAdapter {
23
23
  ensureSessionsDir(): string;
24
- upsertSession(row: SessionRowShape): Promise<void>;
25
- getSession(sessionId: string): Promise<SessionRowShape | undefined>;
24
+ upsertSession(row: SessionRow): Promise<void>;
25
+ getSession(sessionId: string): Promise<SessionRow | undefined>;
26
26
  listSessions(options: {
27
27
  limit: number;
28
28
  parentSessionId?: string;
29
29
  status?: string;
30
- }): Promise<SessionRowShape[]>;
30
+ }): Promise<SessionRow[]>;
31
31
  updateSession(input: PersistedSessionUpdateInput): Promise<{
32
32
  updated: boolean;
33
33
  statusLock: number;
@@ -91,7 +91,7 @@ export declare class UnifiedSessionPersistenceService {
91
91
  handleSubAgentEnd(rootSessionId: string, context: SubAgentEndContext): Promise<void>;
92
92
  private isPidAlive;
93
93
  private reconcileDeadRunningSession;
94
- listSessions(limit?: number): Promise<SessionRowShape[]>;
94
+ listSessions(limit?: number): Promise<SessionRow[]>;
95
95
  reconcileDeadSessions(limit?: number): Promise<number>;
96
96
  deleteSession(sessionId: string): Promise<{
97
97
  deleted: boolean;
@@ -1,11 +1,11 @@
1
1
  import type { AgentConfig, AgentEvent, AgentResult } from "@clinebot/agents";
2
2
  import type { LlmsProviders } from "@clinebot/llms";
3
3
  import type { SessionRecord } from "../../types/sessions";
4
- import type { SessionRowShape } from "../session-service";
4
+ import type { SessionRow } from "../session-service";
5
5
  import type { StoredMessageWithMetadata } from "./types";
6
6
  export declare function extractWorkspaceMetadataFromSystemPrompt(systemPrompt: string): string | undefined;
7
7
  export declare function hasRuntimeHooks(hooks: AgentConfig["hooks"]): boolean;
8
8
  export declare function mergeAgentExtensions(explicitExtensions: AgentConfig["extensions"] | undefined, loadedExtensions: AgentConfig["extensions"] | undefined): AgentConfig["extensions"];
9
9
  export declare function serializeAgentEvent(event: AgentEvent): string;
10
10
  export declare function withLatestAssistantTurnMetadata(messages: LlmsProviders.Message[], result: AgentResult, previousMessages?: LlmsProviders.MessageWithMetadata[]): StoredMessageWithMetadata[];
11
- export declare function toSessionRecord(row: SessionRowShape): SessionRecord;
11
+ export declare function toSessionRecord(row: SessionRow): SessionRecord;
@@ -5,6 +5,7 @@
5
5
  * and are used for both validation and JSON Schema generation.
6
6
  */
7
7
  import { z } from "zod";
8
+ export declare const INPUT_ARG_CHAR_LIMIT = 6000;
8
9
  export declare const ReadFileLineRangeSchema: z.ZodObject<{
9
10
  start_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
10
11
  end_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@clinebot/core",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "main": "./dist/index.node.js",
5
5
  "dependencies": {
6
- "@clinebot/agents": "0.0.12",
7
- "@clinebot/llms": "0.0.12",
8
- "@clinebot/shared": "0.0.12",
6
+ "@clinebot/agents": "0.0.13",
7
+ "@clinebot/llms": "0.0.13",
8
+ "@clinebot/shared": "0.0.13",
9
9
  "@opentelemetry/api": "^1.9.0",
10
10
  "@opentelemetry/api-logs": "^0.56.0",
11
11
  "@opentelemetry/exporter-logs-otlp-http": "^0.56.0",
@@ -1,16 +1,12 @@
1
- import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
1
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
2
2
  import { tmpdir } from "node:os";
3
- import { join, resolve } from "node:path";
4
- import { fileURLToPath } from "node:url";
3
+ import { join } from "node:path";
5
4
  import { describe, expect, it } from "vitest";
6
5
  import {
7
6
  loadAgentPluginFromPath,
8
7
  loadAgentPluginsFromPaths,
9
8
  } from "./plugin-loader";
10
9
 
11
- const TEST_DIR = fileURLToPath(new URL(".", import.meta.url));
12
- const REPO_ROOT = resolve(TEST_DIR, "..", "..", "..", "..");
13
-
14
10
  describe("plugin-loader", () => {
15
11
  it("loads default-exported plugin from path", async () => {
16
12
  const dir = await mkdtemp(join(tmpdir(), "core-plugin-loader-"));
@@ -193,15 +189,19 @@ describe("plugin-loader", () => {
193
189
  const pluginPath = join(dir, "portable-subagents.ts");
194
190
  await writeFile(
195
191
  pluginPath,
196
- await readFile(
197
- resolve(REPO_ROOT, "apps/examples/subagent-plugin/index.ts"),
198
- "utf8",
199
- ),
192
+ [
193
+ "import { resolveClineDataDir } from '@clinebot/shared/storage';",
194
+ "import YAML from 'yaml';",
195
+ "export default {",
196
+ " name: typeof resolveClineDataDir === 'function' ? YAML.stringify({ ok: true }) : 'invalid',",
197
+ " manifest: { capabilities: ['tools'] },",
198
+ "};",
199
+ ].join("\n"),
200
200
  "utf8",
201
201
  );
202
202
 
203
203
  await expect(
204
- loadAgentPluginFromPath(pluginPath, { cwd: dir }),
204
+ loadAgentPluginFromPath(pluginPath, { cwd: dir, useCache: true }),
205
205
  ).rejects.toThrow(/Cannot find (package|module) 'yaml'/i);
206
206
  } finally {
207
207
  await rm(dir, { recursive: true, force: true });
@@ -17,6 +17,7 @@ type PluginLike = {
17
17
  export interface LoadAgentPluginFromPathOptions {
18
18
  exportName?: string;
19
19
  cwd?: string;
20
+ useCache?: boolean;
20
21
  }
21
22
 
22
23
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
@@ -130,12 +131,13 @@ function collectPluginImportAliases(
130
131
 
131
132
  async function importPluginModule(
132
133
  absolutePath: string,
134
+ options: LoadAgentPluginFromPathOptions = {},
133
135
  ): Promise<Record<string, unknown>> {
134
136
  const aliases = collectPluginImportAliases(absolutePath);
135
137
  const jiti = createJiti(absolutePath, {
136
138
  alias: aliases,
137
- cache: false,
138
- requireCache: false,
139
+ cache: options.useCache,
140
+ requireCache: options.useCache,
139
141
  esmResolve: true,
140
142
  interopDefault: false,
141
143
  nativeModules: [...BUILTIN_MODULES],
@@ -149,7 +151,7 @@ export async function loadAgentPluginFromPath(
149
151
  options: LoadAgentPluginFromPathOptions = {},
150
152
  ): Promise<AgentPlugin> {
151
153
  const absolutePath = resolve(options.cwd ?? process.cwd(), pluginPath);
152
- const moduleExports = await importPluginModule(absolutePath);
154
+ const moduleExports = await importPluginModule(absolutePath, options);
153
155
  const exportName = options.exportName ?? "plugin";
154
156
  const plugin = (moduleExports.default ??
155
157
  moduleExports[exportName]) as unknown;
package/src/index.node.ts CHANGED
@@ -138,6 +138,11 @@ export {
138
138
  saveLocalProviderOAuthCredentials,
139
139
  saveLocalProviderSettings,
140
140
  } from "./providers/local-provider-service";
141
+ export type { AvailableRuntimeCommand } from "./runtime/commands";
142
+ export {
143
+ listAvailableRuntimeCommandsFromWatcher,
144
+ resolveRuntimeSlashCommandFromWatcher,
145
+ } from "./runtime/commands";
141
146
  export {
142
147
  formatRulesForSystemPrompt,
143
148
  isRuleEnabled,
@@ -159,6 +164,11 @@ export type {
159
164
  RuntimeBuilderInput,
160
165
  SessionRuntime,
161
166
  } from "./runtime/session-runtime";
167
+ export type { AvailableSkill } from "./runtime/skills";
168
+ export {
169
+ listAvailableSkillsFromWatcher,
170
+ resolveSkillsSlashCommandFromWatcher,
171
+ } from "./runtime/skills";
162
172
  export {
163
173
  type DesktopToolApprovalOptions,
164
174
  requestDesktopToolApproval,
@@ -0,0 +1,98 @@
1
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+ import { createUserInstructionConfigWatcher } from "../agents";
6
+ import {
7
+ listAvailableRuntimeCommandsFromWatcher,
8
+ resolveRuntimeSlashCommandFromWatcher,
9
+ } from "./commands";
10
+
11
+ describe("runtime command registry", () => {
12
+ const tempRoots: string[] = [];
13
+
14
+ afterEach(async () => {
15
+ await Promise.all(
16
+ tempRoots.map((dir) => rm(dir, { recursive: true, force: true })),
17
+ );
18
+ tempRoots.length = 0;
19
+ });
20
+
21
+ it("lists workflow and skill commands together", async () => {
22
+ const tempRoot = await mkdtemp(join(tmpdir(), "core-runtime-commands-"));
23
+ tempRoots.push(tempRoot);
24
+ const skillDir = join(tempRoot, "skills", "debug");
25
+ const workflowsDir = join(tempRoot, "workflows");
26
+ await mkdir(skillDir, { recursive: true });
27
+ await mkdir(workflowsDir, { recursive: true });
28
+ await writeFile(join(skillDir, "SKILL.md"), "Use the debugging skill.");
29
+ await writeFile(
30
+ join(workflowsDir, "release.md"),
31
+ `---
32
+ name: release
33
+ ---
34
+ Run the release workflow.`,
35
+ );
36
+
37
+ const watcher = createUserInstructionConfigWatcher({
38
+ skills: { directories: [join(tempRoot, "skills")] },
39
+ rules: { directories: [] },
40
+ workflows: { directories: [workflowsDir] },
41
+ });
42
+
43
+ try {
44
+ await watcher.start();
45
+ expect(listAvailableRuntimeCommandsFromWatcher(watcher)).toEqual([
46
+ {
47
+ id: "debug",
48
+ name: "debug",
49
+ instructions: "Use the debugging skill.",
50
+ kind: "skill",
51
+ },
52
+ {
53
+ id: "release",
54
+ name: "release",
55
+ instructions: "Run the release workflow.",
56
+ kind: "workflow",
57
+ },
58
+ ]);
59
+ } finally {
60
+ watcher.stop();
61
+ }
62
+ });
63
+
64
+ it("expands skill and workflow slash commands with workflow precedence", async () => {
65
+ const tempRoot = await mkdtemp(join(tmpdir(), "core-runtime-commands-"));
66
+ tempRoots.push(tempRoot);
67
+ const skillDir = join(tempRoot, "skills", "ship");
68
+ const workflowsDir = join(tempRoot, "workflows");
69
+ await mkdir(skillDir, { recursive: true });
70
+ await mkdir(workflowsDir, { recursive: true });
71
+ await writeFile(join(skillDir, "SKILL.md"), "Use the ship skill.");
72
+ await writeFile(
73
+ join(workflowsDir, "ship.md"),
74
+ `---
75
+ name: ship
76
+ ---
77
+ Run the ship workflow.`,
78
+ );
79
+
80
+ const watcher = createUserInstructionConfigWatcher({
81
+ skills: { directories: [join(tempRoot, "skills")] },
82
+ rules: { directories: [] },
83
+ workflows: { directories: [workflowsDir] },
84
+ });
85
+
86
+ try {
87
+ await watcher.start();
88
+ expect(resolveRuntimeSlashCommandFromWatcher("/ship", watcher)).toBe(
89
+ "Run the ship workflow.",
90
+ );
91
+ expect(resolveRuntimeSlashCommandFromWatcher("/ship now", watcher)).toBe(
92
+ "Run the ship workflow. now",
93
+ );
94
+ } finally {
95
+ watcher.stop();
96
+ }
97
+ });
98
+ });
@@ -0,0 +1,83 @@
1
+ import type {
2
+ SkillConfig,
3
+ UserInstructionConfigWatcher,
4
+ WorkflowConfig,
5
+ } from "../agents";
6
+
7
+ export type RuntimeCommandKind = "skill" | "workflow";
8
+
9
+ export type AvailableRuntimeCommand = {
10
+ id: string;
11
+ name: string;
12
+ instructions: string;
13
+ kind: RuntimeCommandKind;
14
+ };
15
+
16
+ type CommandRecord = {
17
+ item: SkillConfig | WorkflowConfig;
18
+ };
19
+
20
+ function isCommandEnabled(command: SkillConfig | WorkflowConfig): boolean {
21
+ return command.disabled !== true;
22
+ }
23
+
24
+ function listCommandsForKind(
25
+ watcher: UserInstructionConfigWatcher,
26
+ kind: RuntimeCommandKind,
27
+ ): AvailableRuntimeCommand[] {
28
+ return [...watcher.getSnapshot(kind).entries()]
29
+ .map(([id, record]) => ({ id, record: record as CommandRecord }))
30
+ .filter(({ record }) => isCommandEnabled(record.item))
31
+ .map(({ id, record }) => ({
32
+ id,
33
+ name: record.item.name,
34
+ instructions: record.item.instructions,
35
+ kind,
36
+ }))
37
+ .sort((a, b) => a.name.localeCompare(b.name));
38
+ }
39
+
40
+ export function listAvailableRuntimeCommandsFromWatcher(
41
+ watcher: UserInstructionConfigWatcher,
42
+ ): AvailableRuntimeCommand[] {
43
+ const byName = new Map<string, AvailableRuntimeCommand>();
44
+ for (const command of [
45
+ ...listCommandsForKind(watcher, "workflow"),
46
+ ...listCommandsForKind(watcher, "skill"),
47
+ ]) {
48
+ if (!byName.has(command.name)) {
49
+ byName.set(command.name, command);
50
+ }
51
+ }
52
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
53
+ }
54
+
55
+ export function resolveRuntimeSlashCommandFromWatcher(
56
+ input: string,
57
+ watcher: UserInstructionConfigWatcher,
58
+ ): string {
59
+ if (!input.startsWith("/") || input.length < 2) {
60
+ return input;
61
+ }
62
+ const match = input.match(/^\/(\S+)/);
63
+ if (!match) {
64
+ return input;
65
+ }
66
+ const name = match[1];
67
+ if (!name) {
68
+ return input;
69
+ }
70
+ const commandLength = name.length + 1;
71
+ const remainder = input.slice(commandLength);
72
+ const matched = listAvailableRuntimeCommandsFromWatcher(watcher).find(
73
+ (command) => command.name === name,
74
+ );
75
+ return matched ? `${matched.instructions}${remainder}` : input;
76
+ }
77
+
78
+ export function listAvailableRuntimeCommandsForKindFromWatcher(
79
+ watcher: UserInstructionConfigWatcher,
80
+ kind: RuntimeCommandKind,
81
+ ): AvailableRuntimeCommand[] {
82
+ return listCommandsForKind(watcher, kind);
83
+ }
@@ -1,3 +1,8 @@
1
+ export {
2
+ type AvailableRuntimeCommand,
3
+ listAvailableRuntimeCommandsFromWatcher,
4
+ resolveRuntimeSlashCommandFromWatcher,
5
+ } from "./commands";
1
6
  export {
2
7
  formatRulesForSystemPrompt,
3
8
  isRuleEnabled,
@@ -16,6 +21,11 @@ export type {
16
21
  RuntimeBuilderInput,
17
22
  SessionRuntime,
18
23
  } from "./session-runtime";
24
+ export {
25
+ type AvailableSkill,
26
+ listAvailableSkillsFromWatcher,
27
+ resolveSkillsSlashCommandFromWatcher,
28
+ } from "./skills";
19
29
  export {
20
30
  type DesktopToolApprovalOptions,
21
31
  requestDesktopToolApproval,
@@ -0,0 +1,44 @@
1
+ import type { UserInstructionConfigWatcher } from "../agents";
2
+ import {
3
+ listAvailableRuntimeCommandsForKindFromWatcher,
4
+ resolveRuntimeSlashCommandFromWatcher,
5
+ } from "./commands";
6
+
7
+ export type AvailableSkill = {
8
+ id: string;
9
+ name: string;
10
+ instructions: string;
11
+ };
12
+
13
+ function matchesLeadingSlashCommand(input: string, name: string): boolean {
14
+ const match = input.match(/^\/(\S+)/);
15
+ return match?.[1] === name;
16
+ }
17
+
18
+ export function listAvailableSkillsFromWatcher(
19
+ watcher: UserInstructionConfigWatcher,
20
+ ): AvailableSkill[] {
21
+ return listAvailableRuntimeCommandsForKindFromWatcher(watcher, "skill").map(
22
+ (skill) => ({
23
+ id: skill.id,
24
+ name: skill.name,
25
+ instructions: skill.instructions,
26
+ }),
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Expands a leading slash command (e.g. "/release") to skill instructions.
32
+ * If the input starts with "/<skill-name>", that prefix is replaced and the
33
+ * remaining input is preserved unchanged.
34
+ */
35
+ export function resolveSkillsSlashCommandFromWatcher(
36
+ input: string,
37
+ watcher: UserInstructionConfigWatcher,
38
+ ): string {
39
+ const resolved = resolveRuntimeSlashCommandFromWatcher(input, watcher);
40
+ const matched = listAvailableSkillsFromWatcher(watcher).some((skill) =>
41
+ matchesLeadingSlashCommand(input, skill.name),
42
+ );
43
+ return matched ? resolved : input;
44
+ }
@@ -1,4 +1,8 @@
1
- import type { UserInstructionConfigWatcher, WorkflowConfig } from "../agents";
1
+ import type { UserInstructionConfigWatcher } from "../agents";
2
+ import {
3
+ listAvailableRuntimeCommandsForKindFromWatcher,
4
+ resolveRuntimeSlashCommandFromWatcher,
5
+ } from "./commands";
2
6
 
3
7
  export type AvailableWorkflow = {
4
8
  id: string;
@@ -6,23 +10,22 @@ export type AvailableWorkflow = {
6
10
  instructions: string;
7
11
  };
8
12
 
9
- function isWorkflowEnabled(workflow: WorkflowConfig): boolean {
10
- return workflow.disabled !== true;
13
+ function matchesLeadingSlashCommand(input: string, name: string): boolean {
14
+ const match = input.match(/^\/(\S+)/);
15
+ return match?.[1] === name;
11
16
  }
12
17
 
13
18
  export function listAvailableWorkflowsFromWatcher(
14
19
  watcher: UserInstructionConfigWatcher,
15
20
  ): AvailableWorkflow[] {
16
- const snapshot = watcher.getSnapshot("workflow");
17
- return [...snapshot.entries()]
18
- .map(([id, record]) => ({ id, workflow: record.item as WorkflowConfig }))
19
- .filter(({ workflow }) => isWorkflowEnabled(workflow))
20
- .map(({ id, workflow }) => ({
21
- id,
22
- name: workflow.name,
23
- instructions: workflow.instructions,
24
- }))
25
- .sort((a, b) => a.name.localeCompare(b.name));
21
+ return listAvailableRuntimeCommandsForKindFromWatcher(
22
+ watcher,
23
+ "workflow",
24
+ ).map((workflow) => ({
25
+ id: workflow.id,
26
+ name: workflow.name,
27
+ instructions: workflow.instructions,
28
+ }));
26
29
  }
27
30
 
28
31
  /**
@@ -34,21 +37,9 @@ export function resolveWorkflowSlashCommandFromWatcher(
34
37
  input: string,
35
38
  watcher: UserInstructionConfigWatcher,
36
39
  ): string {
37
- if (!input.startsWith("/") || input.length < 2) {
38
- return input;
39
- }
40
- const match = input.match(/^\/(\S+)/);
41
- if (!match) {
42
- return input;
43
- }
44
- const workflowName = match[1];
45
- if (!workflowName) {
46
- return input;
47
- }
48
- const commandLength = workflowName.length + 1;
49
- const remainder = input.slice(commandLength);
50
- const matched = listAvailableWorkflowsFromWatcher(watcher).find(
51
- (workflow) => workflow.name === workflowName,
40
+ const resolved = resolveRuntimeSlashCommandFromWatcher(input, watcher);
41
+ const matched = listAvailableWorkflowsFromWatcher(watcher).some((workflow) =>
42
+ matchesLeadingSlashCommand(input, workflow.name),
52
43
  );
53
- return matched ? `${matched.instructions}${remainder}` : input;
44
+ return matched ? resolved : input;
54
45
  }