@serviceme/devtools-core 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -109,7 +109,39 @@ declare function createCopilotAuthRequiredError(): _serviceme_devtools_protocol.
109
109
 
110
110
  declare function copilotPrompt(options: CopilotPromptOptions): Promise<CopilotPromptResult>;
111
111
 
112
+ interface RunCommandOptions {
113
+ args?: string[];
114
+ cwd?: string;
115
+ env?: NodeJS.ProcessEnv;
116
+ timeoutMs?: number;
117
+ stdin?: string;
118
+ shell?: boolean;
119
+ }
120
+ interface RunCommandResult {
121
+ stdout: string;
122
+ stderr: string;
123
+ code: number;
124
+ }
125
+ declare function runCommand(command: string, options?: RunCommandOptions): Promise<RunCommandResult>;
126
+
127
+ interface EnvironmentInspectorOptions {
128
+ /**
129
+ * Override the command runner. Production callers should leave this
130
+ * undefined and rely on the default `runCommand` from `../process/runCommand`;
131
+ * tests inject a fake to drive error/edge branches without spawning
132
+ * real subprocesses.
133
+ */
134
+ runCommand?: typeof runCommand;
135
+ /**
136
+ * Override the detected platform. Defaults to `process.platform`. Tests
137
+ * use this to exercise the Windows-specific branches of the inspector.
138
+ */
139
+ platform?: NodeJS.Platform;
140
+ }
112
141
  declare class EnvironmentInspector {
142
+ private readonly runCommandFn;
143
+ private readonly platform;
144
+ constructor(options?: EnvironmentInspectorOptions);
113
145
  checkEnvironment(): Promise<EnvironmentCheckResult>;
114
146
  checkTool(toolName: KnownEnvironmentTool): Promise<ToolCheckResult>;
115
147
  private getToolPath;
@@ -194,38 +226,6 @@ declare class PidManager {
194
226
  getRunningPid(): number | null;
195
227
  }
196
228
 
197
- declare class SchedulerDaemon {
198
- private readonly workspacePath;
199
- private readonly configManager;
200
- private readonly logManager;
201
- private readonly pidManager;
202
- private readonly logger;
203
- private tickTimer;
204
- private watcher;
205
- private running;
206
- private startTime;
207
- private lastRun;
208
- private taskRunning;
209
- constructor(workspacePath: string);
210
- start(): void;
211
- stop(): void;
212
- private setupConfigWatch;
213
- private tick;
214
- private shouldRun;
215
- private executeTask;
216
- getStatus(): {
217
- running: boolean;
218
- pid: number;
219
- uptimeSeconds: number | null;
220
- tasksRegistered: number;
221
- tasksEnabled: number;
222
- workspacePath: string;
223
- pidFile: string;
224
- };
225
- }
226
- declare function parseIntervalMs(schedule: string): number;
227
- declare function matchesCron(expression: string, date: Date): boolean;
228
-
229
229
  interface ExecutorResult {
230
230
  status: TaskExecutionStatus;
231
231
  output?: string;
@@ -260,6 +260,48 @@ declare class ShellExecutor implements StreamingTaskExecutor {
260
260
 
261
261
  declare function getExecutor(taskType: ScheduledTaskType): TaskExecutor;
262
262
 
263
+ interface SchedulerDaemonOptions {
264
+ /**
265
+ * Override the executor factory. Production callers should leave this
266
+ * undefined and rely on the default `getExecutor` from `../executors`;
267
+ * tests inject a fake to drive the execution branch without spawning
268
+ * real subprocesses.
269
+ */
270
+ getExecutor?: typeof getExecutor;
271
+ }
272
+ declare class SchedulerDaemon {
273
+ private readonly workspacePath;
274
+ private readonly configManager;
275
+ private readonly logManager;
276
+ private readonly pidManager;
277
+ private readonly logger;
278
+ private readonly getExecutor;
279
+ private tickTimer;
280
+ private watcher;
281
+ private running;
282
+ private startTime;
283
+ private lastRun;
284
+ private taskRunning;
285
+ constructor(workspacePath: string, options?: SchedulerDaemonOptions);
286
+ start(): void;
287
+ stop(): void;
288
+ private setupConfigWatch;
289
+ private tick;
290
+ private shouldRun;
291
+ private executeTask;
292
+ getStatus(): {
293
+ running: boolean;
294
+ pid: number;
295
+ uptimeSeconds: number | null;
296
+ tasksRegistered: number;
297
+ tasksEnabled: number;
298
+ workspacePath: string;
299
+ pidFile: string;
300
+ };
301
+ }
302
+ declare function parseIntervalMs(schedule: string): number;
303
+ declare function matchesCron(expression: string, date: Date): boolean;
304
+
263
305
  declare function validateTaskPayload(taskType: ScheduledTaskType, payload: TaskPayload): void;
264
306
  interface CreateTaskInput {
265
307
  name: string;
@@ -429,4 +471,4 @@ declare class SkillStore {
429
471
  declare const unzipFile: (zipPath: string, dest: string) => Promise<void>;
430
472
  declare const moveFiles: (sourceDir: string, destDir: string, overwrite?: boolean) => Promise<void>;
431
473
 
432
- export { type AgentCatalog, AgentCatalogClient, type AgentCatalogClientOptions, type AgentDownloadFile, type AgentInstallScope, type AgentMutateResult, AgentReconciler, type AgentReconcilerDependencies, AgentStore, type AgentStoreFileSystem, type AgentStoreOptions, type AgentsStateFile, type AppendLogInput, type CreateTaskInput, DaemonLogger, type EditTaskInput, EnvironmentInspector, type ExecutorResult, GithubCopilotCliExecutor, HttpRequestExecutor, ImageTools, type InstalledAgent, type JsonTools, type OutputEventCallback, PidManager, ProjectTools, SchedulerDaemon, type ServiceMeLogger, ShellExecutor, type SkillCatalog, SkillCatalogClient, type SkillCatalogClientOptions, type SkillDownloadFile, type SkillMutateResult, SkillReconciler, type SkillReconcilerDependencies, SkillStore, type SkillStoreFileSystem, type SkillStoreOptions, type StreamingExecutorHandle, type StreamingTaskExecutor, TOOL_RISK_MAP, TaskConfigManager, type TaskEventListener, TaskExecutionEngine, type TaskExecutor, TaskLogManager, copilotDoctor, copilotPrompt, createConsoleLogger, createCopilotAuthRequiredError, createCopilotNotInstalledError, createImageTools, createJsonTools, createProjectTools, getExecutor, isCopilotAuthenticated, isStreamingTaskExecutor, matchesCron, moveFiles, noopLogger, parseAgentToolPermissions, parseIntervalMs, resolveTaskExecutionPayload, unzipFile, validateTaskPayload };
474
+ export { type AgentCatalog, AgentCatalogClient, type AgentCatalogClientOptions, type AgentDownloadFile, type AgentInstallScope, type AgentMutateResult, AgentReconciler, type AgentReconcilerDependencies, AgentStore, type AgentStoreFileSystem, type AgentStoreOptions, type AgentsStateFile, type AppendLogInput, type CreateTaskInput, DaemonLogger, type EditTaskInput, EnvironmentInspector, type EnvironmentInspectorOptions, type ExecutorResult, GithubCopilotCliExecutor, HttpRequestExecutor, ImageTools, type InstalledAgent, type JsonTools, type OutputEventCallback, PidManager, ProjectTools, SchedulerDaemon, type ServiceMeLogger, ShellExecutor, type SkillCatalog, SkillCatalogClient, type SkillCatalogClientOptions, type SkillDownloadFile, type SkillMutateResult, SkillReconciler, type SkillReconcilerDependencies, SkillStore, type SkillStoreFileSystem, type SkillStoreOptions, type StreamingExecutorHandle, type StreamingTaskExecutor, TOOL_RISK_MAP, TaskConfigManager, type TaskEventListener, TaskExecutionEngine, type TaskExecutor, TaskLogManager, copilotDoctor, copilotPrompt, createConsoleLogger, createCopilotAuthRequiredError, createCopilotNotInstalledError, createImageTools, createJsonTools, createProjectTools, getExecutor, isCopilotAuthenticated, isStreamingTaskExecutor, matchesCron, moveFiles, noopLogger, parseAgentToolPermissions, parseIntervalMs, resolveTaskExecutionPayload, unzipFile, validateTaskPayload };
package/dist/index.d.ts CHANGED
@@ -109,7 +109,39 @@ declare function createCopilotAuthRequiredError(): _serviceme_devtools_protocol.
109
109
 
110
110
  declare function copilotPrompt(options: CopilotPromptOptions): Promise<CopilotPromptResult>;
111
111
 
112
+ interface RunCommandOptions {
113
+ args?: string[];
114
+ cwd?: string;
115
+ env?: NodeJS.ProcessEnv;
116
+ timeoutMs?: number;
117
+ stdin?: string;
118
+ shell?: boolean;
119
+ }
120
+ interface RunCommandResult {
121
+ stdout: string;
122
+ stderr: string;
123
+ code: number;
124
+ }
125
+ declare function runCommand(command: string, options?: RunCommandOptions): Promise<RunCommandResult>;
126
+
127
+ interface EnvironmentInspectorOptions {
128
+ /**
129
+ * Override the command runner. Production callers should leave this
130
+ * undefined and rely on the default `runCommand` from `../process/runCommand`;
131
+ * tests inject a fake to drive error/edge branches without spawning
132
+ * real subprocesses.
133
+ */
134
+ runCommand?: typeof runCommand;
135
+ /**
136
+ * Override the detected platform. Defaults to `process.platform`. Tests
137
+ * use this to exercise the Windows-specific branches of the inspector.
138
+ */
139
+ platform?: NodeJS.Platform;
140
+ }
112
141
  declare class EnvironmentInspector {
142
+ private readonly runCommandFn;
143
+ private readonly platform;
144
+ constructor(options?: EnvironmentInspectorOptions);
113
145
  checkEnvironment(): Promise<EnvironmentCheckResult>;
114
146
  checkTool(toolName: KnownEnvironmentTool): Promise<ToolCheckResult>;
115
147
  private getToolPath;
@@ -194,38 +226,6 @@ declare class PidManager {
194
226
  getRunningPid(): number | null;
195
227
  }
196
228
 
197
- declare class SchedulerDaemon {
198
- private readonly workspacePath;
199
- private readonly configManager;
200
- private readonly logManager;
201
- private readonly pidManager;
202
- private readonly logger;
203
- private tickTimer;
204
- private watcher;
205
- private running;
206
- private startTime;
207
- private lastRun;
208
- private taskRunning;
209
- constructor(workspacePath: string);
210
- start(): void;
211
- stop(): void;
212
- private setupConfigWatch;
213
- private tick;
214
- private shouldRun;
215
- private executeTask;
216
- getStatus(): {
217
- running: boolean;
218
- pid: number;
219
- uptimeSeconds: number | null;
220
- tasksRegistered: number;
221
- tasksEnabled: number;
222
- workspacePath: string;
223
- pidFile: string;
224
- };
225
- }
226
- declare function parseIntervalMs(schedule: string): number;
227
- declare function matchesCron(expression: string, date: Date): boolean;
228
-
229
229
  interface ExecutorResult {
230
230
  status: TaskExecutionStatus;
231
231
  output?: string;
@@ -260,6 +260,48 @@ declare class ShellExecutor implements StreamingTaskExecutor {
260
260
 
261
261
  declare function getExecutor(taskType: ScheduledTaskType): TaskExecutor;
262
262
 
263
+ interface SchedulerDaemonOptions {
264
+ /**
265
+ * Override the executor factory. Production callers should leave this
266
+ * undefined and rely on the default `getExecutor` from `../executors`;
267
+ * tests inject a fake to drive the execution branch without spawning
268
+ * real subprocesses.
269
+ */
270
+ getExecutor?: typeof getExecutor;
271
+ }
272
+ declare class SchedulerDaemon {
273
+ private readonly workspacePath;
274
+ private readonly configManager;
275
+ private readonly logManager;
276
+ private readonly pidManager;
277
+ private readonly logger;
278
+ private readonly getExecutor;
279
+ private tickTimer;
280
+ private watcher;
281
+ private running;
282
+ private startTime;
283
+ private lastRun;
284
+ private taskRunning;
285
+ constructor(workspacePath: string, options?: SchedulerDaemonOptions);
286
+ start(): void;
287
+ stop(): void;
288
+ private setupConfigWatch;
289
+ private tick;
290
+ private shouldRun;
291
+ private executeTask;
292
+ getStatus(): {
293
+ running: boolean;
294
+ pid: number;
295
+ uptimeSeconds: number | null;
296
+ tasksRegistered: number;
297
+ tasksEnabled: number;
298
+ workspacePath: string;
299
+ pidFile: string;
300
+ };
301
+ }
302
+ declare function parseIntervalMs(schedule: string): number;
303
+ declare function matchesCron(expression: string, date: Date): boolean;
304
+
263
305
  declare function validateTaskPayload(taskType: ScheduledTaskType, payload: TaskPayload): void;
264
306
  interface CreateTaskInput {
265
307
  name: string;
@@ -429,4 +471,4 @@ declare class SkillStore {
429
471
  declare const unzipFile: (zipPath: string, dest: string) => Promise<void>;
430
472
  declare const moveFiles: (sourceDir: string, destDir: string, overwrite?: boolean) => Promise<void>;
431
473
 
432
- export { type AgentCatalog, AgentCatalogClient, type AgentCatalogClientOptions, type AgentDownloadFile, type AgentInstallScope, type AgentMutateResult, AgentReconciler, type AgentReconcilerDependencies, AgentStore, type AgentStoreFileSystem, type AgentStoreOptions, type AgentsStateFile, type AppendLogInput, type CreateTaskInput, DaemonLogger, type EditTaskInput, EnvironmentInspector, type ExecutorResult, GithubCopilotCliExecutor, HttpRequestExecutor, ImageTools, type InstalledAgent, type JsonTools, type OutputEventCallback, PidManager, ProjectTools, SchedulerDaemon, type ServiceMeLogger, ShellExecutor, type SkillCatalog, SkillCatalogClient, type SkillCatalogClientOptions, type SkillDownloadFile, type SkillMutateResult, SkillReconciler, type SkillReconcilerDependencies, SkillStore, type SkillStoreFileSystem, type SkillStoreOptions, type StreamingExecutorHandle, type StreamingTaskExecutor, TOOL_RISK_MAP, TaskConfigManager, type TaskEventListener, TaskExecutionEngine, type TaskExecutor, TaskLogManager, copilotDoctor, copilotPrompt, createConsoleLogger, createCopilotAuthRequiredError, createCopilotNotInstalledError, createImageTools, createJsonTools, createProjectTools, getExecutor, isCopilotAuthenticated, isStreamingTaskExecutor, matchesCron, moveFiles, noopLogger, parseAgentToolPermissions, parseIntervalMs, resolveTaskExecutionPayload, unzipFile, validateTaskPayload };
474
+ export { type AgentCatalog, AgentCatalogClient, type AgentCatalogClientOptions, type AgentDownloadFile, type AgentInstallScope, type AgentMutateResult, AgentReconciler, type AgentReconcilerDependencies, AgentStore, type AgentStoreFileSystem, type AgentStoreOptions, type AgentsStateFile, type AppendLogInput, type CreateTaskInput, DaemonLogger, type EditTaskInput, EnvironmentInspector, type EnvironmentInspectorOptions, type ExecutorResult, GithubCopilotCliExecutor, HttpRequestExecutor, ImageTools, type InstalledAgent, type JsonTools, type OutputEventCallback, PidManager, ProjectTools, SchedulerDaemon, type ServiceMeLogger, ShellExecutor, type SkillCatalog, SkillCatalogClient, type SkillCatalogClientOptions, type SkillDownloadFile, type SkillMutateResult, SkillReconciler, type SkillReconcilerDependencies, SkillStore, type SkillStoreFileSystem, type SkillStoreOptions, type StreamingExecutorHandle, type StreamingTaskExecutor, TOOL_RISK_MAP, TaskConfigManager, type TaskEventListener, TaskExecutionEngine, type TaskExecutor, TaskLogManager, copilotDoctor, copilotPrompt, createConsoleLogger, createCopilotAuthRequiredError, createCopilotNotInstalledError, createImageTools, createJsonTools, createProjectTools, getExecutor, isCopilotAuthenticated, isStreamingTaskExecutor, matchesCron, moveFiles, noopLogger, parseAgentToolPermissions, parseIntervalMs, resolveTaskExecutionPayload, unzipFile, validateTaskPayload };
package/dist/index.js CHANGED
@@ -80,10 +80,10 @@ var AgentCatalogClient = class {
80
80
  }
81
81
  async getCatalog() {
82
82
  if (!this.baseUrl) {
83
- return {
84
- agents: [],
85
- fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
86
- };
83
+ throw (0, import_devtools_protocol.createServicemeError)(
84
+ "workspace_not_found",
85
+ "Agent catalog baseUrl is not configured."
86
+ );
87
87
  }
88
88
  const response = await this.fetchImpl(
89
89
  `${this.baseUrl}/api/v1/marketplace/agents`
@@ -577,6 +577,9 @@ async function copilotPrompt(options) {
577
577
  }
578
578
  return { output, exitCode: 0 };
579
579
  } catch (error) {
580
+ if (error instanceof import_devtools_protocol3.ServicemeProtocolError) {
581
+ throw error;
582
+ }
580
583
  const err = error;
581
584
  if (err.code === "ETIMEDOUT") {
582
585
  throw (0, import_devtools_protocol3.createServicemeError)(
@@ -622,11 +625,13 @@ var TOOL_CHECK_TIMEOUT_MS = {
622
625
  var ERROR_CODE_NOT_FOUND = 127;
623
626
  var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
624
627
  var EnvironmentInspector = class {
628
+ constructor(options = {}) {
629
+ this.runCommandFn = options.runCommand ?? runCommand;
630
+ this.platform = options.platform ?? process.platform;
631
+ }
625
632
  async checkEnvironment() {
626
633
  const results = await Promise.all(
627
- import_devtools_protocol4.KNOWN_ENVIRONMENT_TOOLS.map(
628
- async (tool) => [tool, await this.checkTool(tool)]
629
- )
634
+ import_devtools_protocol4.KNOWN_ENVIRONMENT_TOOLS.map(async (tool) => [tool, await this.checkTool(tool)])
630
635
  );
631
636
  return Object.fromEntries(results);
632
637
  }
@@ -654,8 +659,8 @@ var EnvironmentInspector = class {
654
659
  }
655
660
  async getToolPath(toolName) {
656
661
  try {
657
- const isWindows = process.platform === "win32";
658
- const result = await runCommand(isWindows ? "where" : "which", {
662
+ const isWindows = this.platform === "win32";
663
+ const result = await this.runCommandFn(isWindows ? "where" : "which", {
659
664
  args: [toolName],
660
665
  timeoutMs: this.getToolTimeout(toolName)
661
666
  });
@@ -672,14 +677,12 @@ var EnvironmentInspector = class {
672
677
  */
673
678
  async getToolShimPath(toolName) {
674
679
  try {
675
- const result = await runCommand("where", {
680
+ const result = await this.runCommandFn("where", {
676
681
  args: [toolName],
677
682
  timeoutMs: this.getToolTimeout(toolName)
678
683
  });
679
684
  const candidates = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
680
- const cmdShim = candidates.find(
681
- (line) => line.toLowerCase().endsWith(".cmd")
682
- );
685
+ const cmdShim = candidates.find((line) => line.toLowerCase().endsWith(".cmd"));
683
686
  return cmdShim ?? candidates[0];
684
687
  } catch {
685
688
  return void 0;
@@ -687,16 +690,16 @@ var EnvironmentInspector = class {
687
690
  }
688
691
  async getToolVersion(toolName) {
689
692
  const invocation = await this.getVersionInvocation(toolName);
690
- const result = await runCommand(invocation.command, {
693
+ const result = await this.runCommandFn(invocation.command, {
691
694
  args: invocation.args,
692
695
  timeoutMs: this.getToolTimeout(toolName)
693
696
  });
694
697
  return this.parseVersion(toolName, result.stdout || result.stderr);
695
698
  }
696
699
  async checkNvm() {
697
- if (process.platform === "win32") {
700
+ if (this.platform === "win32") {
698
701
  try {
699
- const result = await runCommand("cmd.exe", {
702
+ const result = await this.runCommandFn("cmd.exe", {
700
703
  args: ["/c", "nvm version"],
701
704
  timeoutMs: this.getToolTimeout("nvm")
702
705
  });
@@ -713,7 +716,7 @@ var EnvironmentInspector = class {
713
716
  }
714
717
  }
715
718
  try {
716
- const result = await runCommand("/bin/bash", {
719
+ const result = await this.runCommandFn("/bin/bash", {
717
720
  args: [
718
721
  "-lc",
719
722
  'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; nvm --version'
@@ -741,7 +744,7 @@ var EnvironmentInspector = class {
741
744
  };
742
745
  }
743
746
  try {
744
- const result = await runCommand("dotnet", {
747
+ const result = await this.runCommandFn("dotnet", {
745
748
  args: ["nuget", "list", "source"],
746
749
  timeoutMs: this.getToolTimeout("nuget")
747
750
  });
@@ -762,7 +765,7 @@ var EnvironmentInspector = class {
762
765
  }
763
766
  }
764
767
  async getVersionInvocation(toolName) {
765
- const isWindows = process.platform === "win32";
768
+ const isWindows = this.platform === "win32";
766
769
  if (isWindows && ["npm", "pnpm", "nrm"].includes(toolName)) {
767
770
  const shim = await this.getToolShimPath(toolName);
768
771
  if (shim) {
@@ -1100,52 +1103,45 @@ var import_devtools_protocol7 = require("@serviceme/devtools-protocol");
1100
1103
  var import_node_fs = require("fs");
1101
1104
  var import_promises = require("fs/promises");
1102
1105
  var import_node_path = require("path");
1103
- var import_yauzl = require("yauzl");
1106
+ var import_yauzl = __toESM(require("yauzl"));
1104
1107
  var unzipFile = (zipPath, dest) => {
1105
1108
  return new Promise((resolve, reject) => {
1106
- (0, import_yauzl.open)(
1107
- zipPath,
1108
- { lazyEntries: true },
1109
- (err, zipfile) => {
1110
- if (err) return reject(err);
1111
- if (!zipfile) return reject(new Error("Failed to open zip file."));
1112
- zipfile.readEntry();
1113
- zipfile.on("entry", (entry) => {
1114
- if (/\/$/.test(entry.fileName)) {
1115
- void (0, import_promises.mkdir)((0, import_node_path.join)(dest, entry.fileName), { recursive: true }).then(() => {
1116
- zipfile.readEntry();
1117
- }).catch(reject);
1118
- } else {
1119
- const outputPath = (0, import_node_path.join)(dest, entry.fileName);
1120
- void (0, import_promises.mkdir)((0, import_node_path.dirname)(outputPath), { recursive: true }).then(() => {
1121
- zipfile.openReadStream(
1122
- entry,
1123
- (streamError, readStream) => {
1124
- if (streamError) return reject(streamError);
1125
- if (!readStream)
1126
- return reject(
1127
- new Error("Failed to open zip entry stream.")
1128
- );
1129
- const writeStream = (0, import_node_fs.createWriteStream)(outputPath);
1130
- readStream.on("error", reject);
1131
- writeStream.on("error", reject);
1132
- writeStream.on("close", () => {
1133
- zipfile.readEntry();
1134
- });
1135
- readStream.pipe(writeStream);
1136
- }
1137
- );
1138
- }).catch(reject);
1139
- }
1140
- });
1141
- zipfile.on("end", () => {
1142
- resolve();
1143
- });
1144
- zipfile.on("error", (zipError) => {
1145
- reject(zipError);
1146
- });
1147
- }
1148
- );
1109
+ import_yauzl.default.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
1110
+ if (err) return reject(err);
1111
+ if (!zipfile) return reject(new Error("Failed to open zip file."));
1112
+ zipfile.readEntry();
1113
+ zipfile.on("entry", (entry) => {
1114
+ if (/\/$/.test(entry.fileName)) {
1115
+ void (0, import_promises.mkdir)((0, import_node_path.join)(dest, entry.fileName), { recursive: true }).then(() => {
1116
+ zipfile.readEntry();
1117
+ }).catch(reject);
1118
+ } else {
1119
+ const outputPath = (0, import_node_path.join)(dest, entry.fileName);
1120
+ void (0, import_promises.mkdir)((0, import_node_path.dirname)(outputPath), { recursive: true }).then(() => {
1121
+ zipfile.openReadStream(
1122
+ entry,
1123
+ (streamError, readStream) => {
1124
+ if (streamError) return reject(streamError);
1125
+ if (!readStream) return reject(new Error("Failed to open zip entry stream."));
1126
+ const writeStream = (0, import_node_fs.createWriteStream)(outputPath);
1127
+ readStream.on("error", reject);
1128
+ writeStream.on("error", reject);
1129
+ writeStream.on("close", () => {
1130
+ zipfile.readEntry();
1131
+ });
1132
+ readStream.pipe(writeStream);
1133
+ }
1134
+ );
1135
+ }).catch(reject);
1136
+ }
1137
+ });
1138
+ zipfile.on("end", () => {
1139
+ resolve();
1140
+ });
1141
+ zipfile.on("error", (zipError) => {
1142
+ reject(zipError);
1143
+ });
1144
+ });
1149
1145
  });
1150
1146
  };
1151
1147
  var tryLstat = async (targetPath) => {
@@ -1169,11 +1165,7 @@ var mergeEntry = async (sourcePath, destPath, overwrite) => {
1169
1165
  await (0, import_promises.mkdir)(destPath, { recursive: true });
1170
1166
  const children = await (0, import_promises.readdir)(sourcePath);
1171
1167
  for (const child of children) {
1172
- await mergeEntry(
1173
- (0, import_node_path.join)(sourcePath, child),
1174
- (0, import_node_path.join)(destPath, child),
1175
- overwrite
1176
- );
1168
+ await mergeEntry((0, import_node_path.join)(sourcePath, child), (0, import_node_path.join)(destPath, child), overwrite);
1177
1169
  }
1178
1170
  await (0, import_promises.rm)(sourcePath, { recursive: true, force: true });
1179
1171
  return;
@@ -1263,23 +1255,15 @@ var ProjectTools = class {
1263
1255
  }
1264
1256
  async makeScriptsExecutable(workspacePath) {
1265
1257
  const isWindows = process.platform === "win32";
1266
- const scripts = await this.findScripts(
1267
- workspacePath,
1268
- isWindows ? [".ps1", ".bat"] : [".sh"]
1269
- );
1258
+ const scripts = await this.findScripts(workspacePath, isWindows ? [".ps1", ".bat"] : [".sh"]);
1270
1259
  let updatedCount = 0;
1271
1260
  if (isWindows) {
1272
- for (const scriptPath of scripts.filter(
1273
- (script) => script.endsWith(".ps1")
1274
- )) {
1261
+ for (const scriptPath of scripts.filter((script) => script.endsWith(".ps1"))) {
1275
1262
  try {
1276
- await runCommand(
1277
- `powershell -Command "Unblock-File -Path '${scriptPath}'"`,
1278
- {
1279
- cwd: workspacePath,
1280
- shell: true
1281
- }
1282
- );
1263
+ await runCommand(`powershell -Command "Unblock-File -Path '${scriptPath}'"`, {
1264
+ cwd: workspacePath,
1265
+ shell: true
1266
+ });
1283
1267
  updatedCount += 1;
1284
1268
  } catch {
1285
1269
  }
@@ -1344,11 +1328,7 @@ var ProjectTools = class {
1344
1328
  if (await this.pathExists(presetManifestPath)) {
1345
1329
  return;
1346
1330
  }
1347
- const projectModePath = path3.join(
1348
- workspacePath,
1349
- ".ms-scaffold",
1350
- "project-mode.json"
1351
- );
1331
+ const projectModePath = path3.join(workspacePath, ".ms-scaffold", "project-mode.json");
1352
1332
  if (!await this.pathExists(projectModePath)) {
1353
1333
  return;
1354
1334
  }
@@ -1402,9 +1382,7 @@ var ProjectTools = class {
1402
1382
  if (directoryNames.length === 1) {
1403
1383
  return directoryNames[0] ?? null;
1404
1384
  }
1405
- const exactMatch = directoryNames.find(
1406
- (directoryName) => directoryName === expectedDirName
1407
- );
1385
+ const exactMatch = directoryNames.find((directoryName) => directoryName === expectedDirName);
1408
1386
  if (exactMatch) {
1409
1387
  return exactMatch;
1410
1388
  }
@@ -1645,10 +1623,8 @@ var GithubCopilotCliExecutor = class {
1645
1623
  windowsHide: true
1646
1624
  });
1647
1625
  if (child.pid) {
1648
- writeDiagnostic(
1649
- `[GithubCopilotCliExecutor] spawned child PID=${child.pid}
1650
- `
1651
- );
1626
+ writeDiagnostic(`[GithubCopilotCliExecutor] spawned child PID=${child.pid}
1627
+ `);
1652
1628
  }
1653
1629
  const timeoutMs = execution.timeoutMs;
1654
1630
  const timer = timeoutMs != null ? setTimeout(() => {
@@ -1666,15 +1642,13 @@ var GithubCopilotCliExecutor = class {
1666
1642
  child.stdout.on("data", (chunk) => {
1667
1643
  const data = chunk.toString();
1668
1644
  stdoutBuf += data;
1669
- if (stdoutBuf.length > MAX_OUTPUT_BYTES)
1670
- stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES);
1645
+ if (stdoutBuf.length > MAX_OUTPUT_BYTES) stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES);
1671
1646
  onOutput("stdout", data);
1672
1647
  });
1673
1648
  child.stderr.on("data", (chunk) => {
1674
1649
  const data = chunk.toString();
1675
1650
  stderrBuf += data;
1676
- if (stderrBuf.length > MAX_OUTPUT_BYTES)
1677
- stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES);
1651
+ if (stderrBuf.length > MAX_OUTPUT_BYTES) stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES);
1678
1652
  onOutput("stderr", data);
1679
1653
  });
1680
1654
  child.on("close", (code) => {
@@ -2003,10 +1977,7 @@ function isRecord(value) {
2003
1977
  }
2004
1978
  function requireNonEmptyString(payload, field, taskType) {
2005
1979
  if (!isRecord(payload) || typeof payload[field] !== "string" || !payload[field].trim()) {
2006
- throw (0, import_devtools_protocol8.createServicemeError)(
2007
- "invalid_payload",
2008
- `${taskType} payload ${field} is required`
2009
- );
1980
+ throw (0, import_devtools_protocol8.createServicemeError)("invalid_payload", `${taskType} payload ${field} is required`);
2010
1981
  }
2011
1982
  }
2012
1983
  function validateTaskPayload(taskType, payload) {
@@ -2169,9 +2140,7 @@ var TaskExecutionEngine = class {
2169
2140
  if (policy === "reject") {
2170
2141
  const existingIds = this.taskExecutions.get(snapshot.taskId);
2171
2142
  if (existingIds && existingIds.size > 0) {
2172
- throw new Error(
2173
- `TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`
2174
- );
2143
+ throw new Error(`TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`);
2175
2144
  }
2176
2145
  }
2177
2146
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -2436,7 +2405,7 @@ var TaskLogManager = class {
2436
2405
  var TICK_INTERVAL = 1e3;
2437
2406
  var MIN_SCHEDULE_INTERVAL = 1e3;
2438
2407
  var SchedulerDaemon = class {
2439
- constructor(workspacePath) {
2408
+ constructor(workspacePath, options = {}) {
2440
2409
  this.tickTimer = null;
2441
2410
  this.watcher = null;
2442
2411
  this.running = false;
@@ -2449,6 +2418,7 @@ var SchedulerDaemon = class {
2449
2418
  this.logManager = new TaskLogManager(workspacePath);
2450
2419
  this.pidManager = new PidManager(workspacePath);
2451
2420
  this.logger = new DaemonLogger(workspacePath);
2421
+ this.getExecutor = options.getExecutor ?? getExecutor;
2452
2422
  }
2453
2423
  start() {
2454
2424
  if (this.running) return;
@@ -2554,7 +2524,7 @@ var SchedulerDaemon = class {
2554
2524
  this.workspacePath
2555
2525
  );
2556
2526
  validateTaskPayload(task.taskType, executionPayload);
2557
- const executor = getExecutor(task.taskType);
2527
+ const executor = this.getExecutor(task.taskType);
2558
2528
  const result = await executor.execute(executionPayload);
2559
2529
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
2560
2530
  const durationMs = Date.now() - startMs;
@@ -2659,10 +2629,10 @@ var SkillCatalogClient = class {
2659
2629
  }
2660
2630
  async getCatalog() {
2661
2631
  if (!this.baseUrl) {
2662
- return {
2663
- skills: [],
2664
- fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
2665
- };
2632
+ throw (0, import_devtools_protocol9.createServicemeError)(
2633
+ "workspace_not_found",
2634
+ "Skill catalog baseUrl is not configured."
2635
+ );
2666
2636
  }
2667
2637
  const response = await this.fetchImpl(
2668
2638
  `${this.baseUrl}/api/v1/marketplace/skills`
package/dist/index.mjs CHANGED
@@ -14,10 +14,10 @@ var AgentCatalogClient = class {
14
14
  }
15
15
  async getCatalog() {
16
16
  if (!this.baseUrl) {
17
- return {
18
- agents: [],
19
- fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
20
- };
17
+ throw createServicemeError(
18
+ "workspace_not_found",
19
+ "Agent catalog baseUrl is not configured."
20
+ );
21
21
  }
22
22
  const response = await this.fetchImpl(
23
23
  `${this.baseUrl}/api/v1/marketplace/agents`
@@ -474,7 +474,8 @@ function createCopilotAuthRequiredError() {
474
474
  // src/copilot/prompt.ts
475
475
  import {
476
476
  COPILOT_ERROR_CODES as COPILOT_ERROR_CODES2,
477
- createServicemeError as createServicemeError3
477
+ createServicemeError as createServicemeError3,
478
+ ServicemeProtocolError
478
479
  } from "@serviceme/devtools-protocol";
479
480
  var COPILOT_COMMAND2 = "copilot";
480
481
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -517,6 +518,9 @@ async function copilotPrompt(options) {
517
518
  }
518
519
  return { output, exitCode: 0 };
519
520
  } catch (error) {
521
+ if (error instanceof ServicemeProtocolError) {
522
+ throw error;
523
+ }
520
524
  const err = error;
521
525
  if (err.code === "ETIMEDOUT") {
522
526
  throw createServicemeError3(
@@ -565,11 +569,13 @@ var TOOL_CHECK_TIMEOUT_MS = {
565
569
  var ERROR_CODE_NOT_FOUND = 127;
566
570
  var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
567
571
  var EnvironmentInspector = class {
572
+ constructor(options = {}) {
573
+ this.runCommandFn = options.runCommand ?? runCommand;
574
+ this.platform = options.platform ?? process.platform;
575
+ }
568
576
  async checkEnvironment() {
569
577
  const results = await Promise.all(
570
- KNOWN_ENVIRONMENT_TOOLS.map(
571
- async (tool) => [tool, await this.checkTool(tool)]
572
- )
578
+ KNOWN_ENVIRONMENT_TOOLS.map(async (tool) => [tool, await this.checkTool(tool)])
573
579
  );
574
580
  return Object.fromEntries(results);
575
581
  }
@@ -597,8 +603,8 @@ var EnvironmentInspector = class {
597
603
  }
598
604
  async getToolPath(toolName) {
599
605
  try {
600
- const isWindows = process.platform === "win32";
601
- const result = await runCommand(isWindows ? "where" : "which", {
606
+ const isWindows = this.platform === "win32";
607
+ const result = await this.runCommandFn(isWindows ? "where" : "which", {
602
608
  args: [toolName],
603
609
  timeoutMs: this.getToolTimeout(toolName)
604
610
  });
@@ -615,14 +621,12 @@ var EnvironmentInspector = class {
615
621
  */
616
622
  async getToolShimPath(toolName) {
617
623
  try {
618
- const result = await runCommand("where", {
624
+ const result = await this.runCommandFn("where", {
619
625
  args: [toolName],
620
626
  timeoutMs: this.getToolTimeout(toolName)
621
627
  });
622
628
  const candidates = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
623
- const cmdShim = candidates.find(
624
- (line) => line.toLowerCase().endsWith(".cmd")
625
- );
629
+ const cmdShim = candidates.find((line) => line.toLowerCase().endsWith(".cmd"));
626
630
  return cmdShim ?? candidates[0];
627
631
  } catch {
628
632
  return void 0;
@@ -630,16 +634,16 @@ var EnvironmentInspector = class {
630
634
  }
631
635
  async getToolVersion(toolName) {
632
636
  const invocation = await this.getVersionInvocation(toolName);
633
- const result = await runCommand(invocation.command, {
637
+ const result = await this.runCommandFn(invocation.command, {
634
638
  args: invocation.args,
635
639
  timeoutMs: this.getToolTimeout(toolName)
636
640
  });
637
641
  return this.parseVersion(toolName, result.stdout || result.stderr);
638
642
  }
639
643
  async checkNvm() {
640
- if (process.platform === "win32") {
644
+ if (this.platform === "win32") {
641
645
  try {
642
- const result = await runCommand("cmd.exe", {
646
+ const result = await this.runCommandFn("cmd.exe", {
643
647
  args: ["/c", "nvm version"],
644
648
  timeoutMs: this.getToolTimeout("nvm")
645
649
  });
@@ -656,7 +660,7 @@ var EnvironmentInspector = class {
656
660
  }
657
661
  }
658
662
  try {
659
- const result = await runCommand("/bin/bash", {
663
+ const result = await this.runCommandFn("/bin/bash", {
660
664
  args: [
661
665
  "-lc",
662
666
  'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; nvm --version'
@@ -684,7 +688,7 @@ var EnvironmentInspector = class {
684
688
  };
685
689
  }
686
690
  try {
687
- const result = await runCommand("dotnet", {
691
+ const result = await this.runCommandFn("dotnet", {
688
692
  args: ["nuget", "list", "source"],
689
693
  timeoutMs: this.getToolTimeout("nuget")
690
694
  });
@@ -705,7 +709,7 @@ var EnvironmentInspector = class {
705
709
  }
706
710
  }
707
711
  async getVersionInvocation(toolName) {
708
- const isWindows = process.platform === "win32";
712
+ const isWindows = this.platform === "win32";
709
713
  if (isWindows && ["npm", "pnpm", "nrm"].includes(toolName)) {
710
714
  const shim = await this.getToolShimPath(toolName);
711
715
  if (shim) {
@@ -1048,62 +1052,47 @@ import {
1048
1052
 
1049
1053
  // src/utils/fileUtils.ts
1050
1054
  import { constants, createWriteStream } from "fs";
1051
- import {
1052
- access as access2,
1053
- copyFile,
1054
- lstat,
1055
- mkdir,
1056
- readdir,
1057
- rename,
1058
- rm
1059
- } from "fs/promises";
1055
+ import { access as access2, copyFile, lstat, mkdir, readdir, rename, rm } from "fs/promises";
1060
1056
  import { dirname as dirname3, join as join3 } from "path";
1061
- import { open } from "yauzl";
1057
+ import yauzl from "yauzl";
1062
1058
  var unzipFile = (zipPath, dest) => {
1063
1059
  return new Promise((resolve, reject) => {
1064
- open(
1065
- zipPath,
1066
- { lazyEntries: true },
1067
- (err, zipfile) => {
1068
- if (err) return reject(err);
1069
- if (!zipfile) return reject(new Error("Failed to open zip file."));
1070
- zipfile.readEntry();
1071
- zipfile.on("entry", (entry) => {
1072
- if (/\/$/.test(entry.fileName)) {
1073
- void mkdir(join3(dest, entry.fileName), { recursive: true }).then(() => {
1074
- zipfile.readEntry();
1075
- }).catch(reject);
1076
- } else {
1077
- const outputPath = join3(dest, entry.fileName);
1078
- void mkdir(dirname3(outputPath), { recursive: true }).then(() => {
1079
- zipfile.openReadStream(
1080
- entry,
1081
- (streamError, readStream) => {
1082
- if (streamError) return reject(streamError);
1083
- if (!readStream)
1084
- return reject(
1085
- new Error("Failed to open zip entry stream.")
1086
- );
1087
- const writeStream = createWriteStream(outputPath);
1088
- readStream.on("error", reject);
1089
- writeStream.on("error", reject);
1090
- writeStream.on("close", () => {
1091
- zipfile.readEntry();
1092
- });
1093
- readStream.pipe(writeStream);
1094
- }
1095
- );
1096
- }).catch(reject);
1097
- }
1098
- });
1099
- zipfile.on("end", () => {
1100
- resolve();
1101
- });
1102
- zipfile.on("error", (zipError) => {
1103
- reject(zipError);
1104
- });
1105
- }
1106
- );
1060
+ yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
1061
+ if (err) return reject(err);
1062
+ if (!zipfile) return reject(new Error("Failed to open zip file."));
1063
+ zipfile.readEntry();
1064
+ zipfile.on("entry", (entry) => {
1065
+ if (/\/$/.test(entry.fileName)) {
1066
+ void mkdir(join3(dest, entry.fileName), { recursive: true }).then(() => {
1067
+ zipfile.readEntry();
1068
+ }).catch(reject);
1069
+ } else {
1070
+ const outputPath = join3(dest, entry.fileName);
1071
+ void mkdir(dirname3(outputPath), { recursive: true }).then(() => {
1072
+ zipfile.openReadStream(
1073
+ entry,
1074
+ (streamError, readStream) => {
1075
+ if (streamError) return reject(streamError);
1076
+ if (!readStream) return reject(new Error("Failed to open zip entry stream."));
1077
+ const writeStream = createWriteStream(outputPath);
1078
+ readStream.on("error", reject);
1079
+ writeStream.on("error", reject);
1080
+ writeStream.on("close", () => {
1081
+ zipfile.readEntry();
1082
+ });
1083
+ readStream.pipe(writeStream);
1084
+ }
1085
+ );
1086
+ }).catch(reject);
1087
+ }
1088
+ });
1089
+ zipfile.on("end", () => {
1090
+ resolve();
1091
+ });
1092
+ zipfile.on("error", (zipError) => {
1093
+ reject(zipError);
1094
+ });
1095
+ });
1107
1096
  });
1108
1097
  };
1109
1098
  var tryLstat = async (targetPath) => {
@@ -1127,11 +1116,7 @@ var mergeEntry = async (sourcePath, destPath, overwrite) => {
1127
1116
  await mkdir(destPath, { recursive: true });
1128
1117
  const children = await readdir(sourcePath);
1129
1118
  for (const child of children) {
1130
- await mergeEntry(
1131
- join3(sourcePath, child),
1132
- join3(destPath, child),
1133
- overwrite
1134
- );
1119
+ await mergeEntry(join3(sourcePath, child), join3(destPath, child), overwrite);
1135
1120
  }
1136
1121
  await rm(sourcePath, { recursive: true, force: true });
1137
1122
  return;
@@ -1221,23 +1206,15 @@ var ProjectTools = class {
1221
1206
  }
1222
1207
  async makeScriptsExecutable(workspacePath) {
1223
1208
  const isWindows = process.platform === "win32";
1224
- const scripts = await this.findScripts(
1225
- workspacePath,
1226
- isWindows ? [".ps1", ".bat"] : [".sh"]
1227
- );
1209
+ const scripts = await this.findScripts(workspacePath, isWindows ? [".ps1", ".bat"] : [".sh"]);
1228
1210
  let updatedCount = 0;
1229
1211
  if (isWindows) {
1230
- for (const scriptPath of scripts.filter(
1231
- (script) => script.endsWith(".ps1")
1232
- )) {
1212
+ for (const scriptPath of scripts.filter((script) => script.endsWith(".ps1"))) {
1233
1213
  try {
1234
- await runCommand(
1235
- `powershell -Command "Unblock-File -Path '${scriptPath}'"`,
1236
- {
1237
- cwd: workspacePath,
1238
- shell: true
1239
- }
1240
- );
1214
+ await runCommand(`powershell -Command "Unblock-File -Path '${scriptPath}'"`, {
1215
+ cwd: workspacePath,
1216
+ shell: true
1217
+ });
1241
1218
  updatedCount += 1;
1242
1219
  } catch {
1243
1220
  }
@@ -1302,11 +1279,7 @@ var ProjectTools = class {
1302
1279
  if (await this.pathExists(presetManifestPath)) {
1303
1280
  return;
1304
1281
  }
1305
- const projectModePath = path3.join(
1306
- workspacePath,
1307
- ".ms-scaffold",
1308
- "project-mode.json"
1309
- );
1282
+ const projectModePath = path3.join(workspacePath, ".ms-scaffold", "project-mode.json");
1310
1283
  if (!await this.pathExists(projectModePath)) {
1311
1284
  return;
1312
1285
  }
@@ -1360,9 +1333,7 @@ var ProjectTools = class {
1360
1333
  if (directoryNames.length === 1) {
1361
1334
  return directoryNames[0] ?? null;
1362
1335
  }
1363
- const exactMatch = directoryNames.find(
1364
- (directoryName) => directoryName === expectedDirName
1365
- );
1336
+ const exactMatch = directoryNames.find((directoryName) => directoryName === expectedDirName);
1366
1337
  if (exactMatch) {
1367
1338
  return exactMatch;
1368
1339
  }
@@ -1603,10 +1574,8 @@ var GithubCopilotCliExecutor = class {
1603
1574
  windowsHide: true
1604
1575
  });
1605
1576
  if (child.pid) {
1606
- writeDiagnostic(
1607
- `[GithubCopilotCliExecutor] spawned child PID=${child.pid}
1608
- `
1609
- );
1577
+ writeDiagnostic(`[GithubCopilotCliExecutor] spawned child PID=${child.pid}
1578
+ `);
1610
1579
  }
1611
1580
  const timeoutMs = execution.timeoutMs;
1612
1581
  const timer = timeoutMs != null ? setTimeout(() => {
@@ -1624,15 +1593,13 @@ var GithubCopilotCliExecutor = class {
1624
1593
  child.stdout.on("data", (chunk) => {
1625
1594
  const data = chunk.toString();
1626
1595
  stdoutBuf += data;
1627
- if (stdoutBuf.length > MAX_OUTPUT_BYTES)
1628
- stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES);
1596
+ if (stdoutBuf.length > MAX_OUTPUT_BYTES) stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES);
1629
1597
  onOutput("stdout", data);
1630
1598
  });
1631
1599
  child.stderr.on("data", (chunk) => {
1632
1600
  const data = chunk.toString();
1633
1601
  stderrBuf += data;
1634
- if (stderrBuf.length > MAX_OUTPUT_BYTES)
1635
- stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES);
1602
+ if (stderrBuf.length > MAX_OUTPUT_BYTES) stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES);
1636
1603
  onOutput("stderr", data);
1637
1604
  });
1638
1605
  child.on("close", (code) => {
@@ -1961,10 +1928,7 @@ function isRecord(value) {
1961
1928
  }
1962
1929
  function requireNonEmptyString(payload, field, taskType) {
1963
1930
  if (!isRecord(payload) || typeof payload[field] !== "string" || !payload[field].trim()) {
1964
- throw createServicemeError8(
1965
- "invalid_payload",
1966
- `${taskType} payload ${field} is required`
1967
- );
1931
+ throw createServicemeError8("invalid_payload", `${taskType} payload ${field} is required`);
1968
1932
  }
1969
1933
  }
1970
1934
  function validateTaskPayload(taskType, payload) {
@@ -2127,9 +2091,7 @@ var TaskExecutionEngine = class {
2127
2091
  if (policy === "reject") {
2128
2092
  const existingIds = this.taskExecutions.get(snapshot.taskId);
2129
2093
  if (existingIds && existingIds.size > 0) {
2130
- throw new Error(
2131
- `TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`
2132
- );
2094
+ throw new Error(`TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`);
2133
2095
  }
2134
2096
  }
2135
2097
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -2394,7 +2356,7 @@ var TaskLogManager = class {
2394
2356
  var TICK_INTERVAL = 1e3;
2395
2357
  var MIN_SCHEDULE_INTERVAL = 1e3;
2396
2358
  var SchedulerDaemon = class {
2397
- constructor(workspacePath) {
2359
+ constructor(workspacePath, options = {}) {
2398
2360
  this.tickTimer = null;
2399
2361
  this.watcher = null;
2400
2362
  this.running = false;
@@ -2407,6 +2369,7 @@ var SchedulerDaemon = class {
2407
2369
  this.logManager = new TaskLogManager(workspacePath);
2408
2370
  this.pidManager = new PidManager(workspacePath);
2409
2371
  this.logger = new DaemonLogger(workspacePath);
2372
+ this.getExecutor = options.getExecutor ?? getExecutor;
2410
2373
  }
2411
2374
  start() {
2412
2375
  if (this.running) return;
@@ -2512,7 +2475,7 @@ var SchedulerDaemon = class {
2512
2475
  this.workspacePath
2513
2476
  );
2514
2477
  validateTaskPayload(task.taskType, executionPayload);
2515
- const executor = getExecutor(task.taskType);
2478
+ const executor = this.getExecutor(task.taskType);
2516
2479
  const result = await executor.execute(executionPayload);
2517
2480
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
2518
2481
  const durationMs = Date.now() - startMs;
@@ -2617,10 +2580,10 @@ var SkillCatalogClient = class {
2617
2580
  }
2618
2581
  async getCatalog() {
2619
2582
  if (!this.baseUrl) {
2620
- return {
2621
- skills: [],
2622
- fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
2623
- };
2583
+ throw createServicemeError9(
2584
+ "workspace_not_found",
2585
+ "Skill catalog baseUrl is not configured."
2586
+ );
2624
2587
  }
2625
2588
  const response = await this.fetchImpl(
2626
2589
  `${this.baseUrl}/api/v1/marketplace/skills`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serviceme/devtools-core",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Reusable Node.js core capabilities powering the SERVICEME CLI and integrations.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "repository": {
@@ -36,7 +36,7 @@
36
36
  "comment-json": "^4.5.1",
37
37
  "json5": "^2.2.3",
38
38
  "yauzl": "^3.2.0",
39
- "@serviceme/devtools-protocol": "0.1.7"
39
+ "@serviceme/devtools-protocol": "0.1.9"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/node": "^24",
@@ -47,6 +47,7 @@
47
47
  },
48
48
  "scripts": {
49
49
  "watch": "tsup src/index.ts --format cjs,esm --dts --watch",
50
+ "lint": "biome check src test",
50
51
  "build": "tsup src/index.ts --format cjs,esm --dts",
51
52
  "typecheck": "tsc --noEmit",
52
53
  "test": "pnpm run build && node --test test/**/*.test.js && node --import tsx --test src/**/__tests__/**/*.test.ts",