@serviceme/devtools-cli 0.0.6 → 0.1.2

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 (3) hide show
  1. package/dist/bridgeServer.js +538 -500
  2. package/dist/cli.js +13543 -11772
  3. package/package.json +6 -4
@@ -5,8 +5,8 @@ var readline = require('readline');
5
5
  var child_process = require('child_process');
6
6
  require('fs/promises');
7
7
  require('path');
8
- var crypto = require('crypto');
9
8
  require('fs');
9
+ require('crypto');
10
10
 
11
11
  function _interopNamespace(e) {
12
12
  if (e && e.__esModule) return e;
@@ -7926,10 +7926,10 @@ var require_stringify = __commonJS({
7926
7926
  replacer = null;
7927
7927
  indent = EMPTY;
7928
7928
  };
7929
- var join4 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + LF + gap : two ? two.trimRight() + LF + gap : EMPTY;
7929
+ var join8 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + LF + gap : two ? two.trimRight() + LF + gap : EMPTY;
7930
7930
  var join_content = (inside, value, gap) => {
7931
7931
  const comment = process_comments(value, PREFIX_BEFORE, gap + indent, true);
7932
- return join4(comment, inside, gap);
7932
+ return join8(comment, inside, gap);
7933
7933
  };
7934
7934
  var array_stringify = (value, gap) => {
7935
7935
  const deeper_gap = gap + indent;
@@ -7940,7 +7940,7 @@ var require_stringify = __commonJS({
7940
7940
  if (i !== 0) {
7941
7941
  inside += COMMA;
7942
7942
  }
7943
- const before = join4(
7943
+ const before = join8(
7944
7944
  after_comma,
7945
7945
  process_comments(value, BEFORE(i), deeper_gap),
7946
7946
  deeper_gap
@@ -7950,7 +7950,7 @@ var require_stringify = __commonJS({
7950
7950
  inside += process_comments(value, AFTER_VALUE(i), deeper_gap);
7951
7951
  after_comma = process_comments(value, AFTER(i), deeper_gap);
7952
7952
  }
7953
- inside += join4(
7953
+ inside += join8(
7954
7954
  after_comma,
7955
7955
  process_comments(value, PREFIX_AFTER, deeper_gap),
7956
7956
  deeper_gap
@@ -7975,7 +7975,7 @@ var require_stringify = __commonJS({
7975
7975
  inside += COMMA;
7976
7976
  }
7977
7977
  first = false;
7978
- const before = join4(
7978
+ const before = join8(
7979
7979
  after_comma,
7980
7980
  process_comments(value, BEFORE(key), deeper_gap),
7981
7981
  deeper_gap
@@ -7985,7 +7985,7 @@ var require_stringify = __commonJS({
7985
7985
  after_comma = process_comments(value, AFTER(key), deeper_gap);
7986
7986
  };
7987
7987
  keys.forEach(iteratee);
7988
- inside += join4(
7988
+ inside += join8(
7989
7989
  after_comma,
7990
7990
  process_comments(value, PREFIX_AFTER, deeper_gap),
7991
7991
  deeper_gap
@@ -8143,7 +8143,7 @@ var require_pend = __commonJS({
8143
8143
  // ../../node_modules/.pnpm/yauzl@3.2.0/node_modules/yauzl/fd-slicer.js
8144
8144
  var require_fd_slicer = __commonJS({
8145
8145
  "../../node_modules/.pnpm/yauzl@3.2.0/node_modules/yauzl/fd-slicer.js"(exports$1) {
8146
- var fs3 = __require("fs");
8146
+ var fs8 = __require("fs");
8147
8147
  var util = __require("util");
8148
8148
  var stream = __require("stream");
8149
8149
  var Readable = stream.Readable;
@@ -8168,7 +8168,7 @@ var require_fd_slicer = __commonJS({
8168
8168
  FdSlicer.prototype.read = function(buffer, offset, length, position, callback) {
8169
8169
  var self = this;
8170
8170
  self.pend.go(function(cb) {
8171
- fs3.read(self.fd, buffer, offset, length, position, function(err, bytesRead, buffer2) {
8171
+ fs8.read(self.fd, buffer, offset, length, position, function(err, bytesRead, buffer2) {
8172
8172
  cb();
8173
8173
  callback(err, bytesRead, buffer2);
8174
8174
  });
@@ -8177,7 +8177,7 @@ var require_fd_slicer = __commonJS({
8177
8177
  FdSlicer.prototype.write = function(buffer, offset, length, position, callback) {
8178
8178
  var self = this;
8179
8179
  self.pend.go(function(cb) {
8180
- fs3.write(self.fd, buffer, offset, length, position, function(err, written, buffer2) {
8180
+ fs8.write(self.fd, buffer, offset, length, position, function(err, written, buffer2) {
8181
8181
  cb();
8182
8182
  callback(err, written, buffer2);
8183
8183
  });
@@ -8198,7 +8198,7 @@ var require_fd_slicer = __commonJS({
8198
8198
  if (self.refCount > 0) return;
8199
8199
  if (self.refCount < 0) throw new Error("invalid unref");
8200
8200
  if (self.autoClose) {
8201
- fs3.close(self.fd, onCloseDone);
8201
+ fs8.close(self.fd, onCloseDone);
8202
8202
  }
8203
8203
  function onCloseDone(err) {
8204
8204
  if (err) {
@@ -8235,7 +8235,7 @@ var require_fd_slicer = __commonJS({
8235
8235
  self.context.pend.go(function(cb) {
8236
8236
  if (self.destroyed) return cb();
8237
8237
  var buffer = Buffer.allocUnsafe(toRead);
8238
- fs3.read(self.context.fd, buffer, 0, toRead, self.pos, function(err, bytesRead) {
8238
+ fs8.read(self.context.fd, buffer, 0, toRead, self.pos, function(err, bytesRead) {
8239
8239
  if (err) {
8240
8240
  self.destroy(err);
8241
8241
  } else if (bytesRead === 0) {
@@ -8282,7 +8282,7 @@ var require_fd_slicer = __commonJS({
8282
8282
  }
8283
8283
  self.context.pend.go(function(cb) {
8284
8284
  if (self.destroyed) return cb();
8285
- fs3.write(self.context.fd, buffer, 0, buffer.length, self.pos, function(err2, bytes) {
8285
+ fs8.write(self.context.fd, buffer, 0, buffer.length, self.pos, function(err2, bytes) {
8286
8286
  if (err2) {
8287
8287
  self.destroy();
8288
8288
  cb();
@@ -8720,7 +8720,7 @@ var require_buffer_crc32 = __commonJS({
8720
8720
  // ../../node_modules/.pnpm/yauzl@3.2.0/node_modules/yauzl/index.js
8721
8721
  var require_yauzl = __commonJS({
8722
8722
  "../../node_modules/.pnpm/yauzl@3.2.0/node_modules/yauzl/index.js"(exports$1) {
8723
- var fs3 = __require("fs");
8723
+ var fs8 = __require("fs");
8724
8724
  var zlib = __require("zlib");
8725
8725
  var fd_slicer = require_fd_slicer();
8726
8726
  var crc32 = require_buffer_crc32();
@@ -8741,7 +8741,7 @@ var require_yauzl = __commonJS({
8741
8741
  exports$1.Entry = Entry;
8742
8742
  exports$1.LocalFileHeader = LocalFileHeader;
8743
8743
  exports$1.RandomAccessReader = RandomAccessReader;
8744
- function open2(path3, options2, callback) {
8744
+ function open2(path7, options2, callback) {
8745
8745
  if (typeof options2 === "function") {
8746
8746
  callback = options2;
8747
8747
  options2 = null;
@@ -8753,10 +8753,10 @@ var require_yauzl = __commonJS({
8753
8753
  if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
8754
8754
  if (options2.strictFileNames == null) options2.strictFileNames = false;
8755
8755
  if (callback == null) callback = defaultCallback;
8756
- fs3.open(path3, "r", function(err, fd) {
8756
+ fs8.open(path7, "r", function(err, fd) {
8757
8757
  if (err) return callback(err);
8758
8758
  fromFd(fd, options2, function(err2, zipfile) {
8759
- if (err2) fs3.close(fd, defaultCallback);
8759
+ if (err2) fs8.close(fd, defaultCallback);
8760
8760
  callback(err2, zipfile);
8761
8761
  });
8762
8762
  });
@@ -8773,7 +8773,7 @@ var require_yauzl = __commonJS({
8773
8773
  if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
8774
8774
  if (options2.strictFileNames == null) options2.strictFileNames = false;
8775
8775
  if (callback == null) callback = defaultCallback;
8776
- fs3.fstat(fd, function(err, stats) {
8776
+ fs8.fstat(fd, function(err, stats) {
8777
8777
  if (err) return callback(err);
8778
8778
  var reader = fd_slicer.createFromFd(fd, { autoClose: true });
8779
8779
  fromRandomAccessReader(reader, stats.size, options2, callback);
@@ -9458,21 +9458,20 @@ var require_yauzl = __commonJS({
9458
9458
  });
9459
9459
 
9460
9460
  // ../../packages/serviceme-protocol/src/bridge.ts
9461
- var SERVICEME_PROTOCOL_VERSION = 1;
9461
+ var SERVICEME_PROTOCOL_VERSION = 2;
9462
9462
  function isRecord(value) {
9463
9463
  return typeof value === "object" && value !== null;
9464
9464
  }
9465
+ function isValidProtocolVersion(value) {
9466
+ return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= SERVICEME_PROTOCOL_VERSION;
9467
+ }
9465
9468
  var BRIDGE_METHODS = [
9466
9469
  "system.hello",
9467
9470
  "system.ping",
9468
9471
  "system.shutdown",
9469
- "opencode.server.ensure",
9470
- "opencode.server.status",
9471
- "opencode.session.create",
9472
- "opencode.session.prompt",
9473
- "opencode.session.status",
9474
- "opencode.session.abort",
9475
- "opencode.session.dispose"
9472
+ "task.execute",
9473
+ "task.cancel",
9474
+ "task.list-running"
9476
9475
  ];
9477
9476
  function isBridgeMethod(value) {
9478
9477
  return typeof value === "string" && BRIDGE_METHODS.includes(value);
@@ -9481,7 +9480,7 @@ function isBridgeRequest(value) {
9481
9480
  if (!isRecord(value)) {
9482
9481
  return false;
9483
9482
  }
9484
- return value.protocolVersion === SERVICEME_PROTOCOL_VERSION && value.kind === "request" && typeof value.id === "string" && isBridgeMethod(value.method) && "params" in value;
9483
+ return isValidProtocolVersion(value.protocolVersion) && value.kind === "request" && typeof value.id === "string" && isBridgeMethod(value.method) && "params" in value;
9485
9484
  }
9486
9485
 
9487
9486
  // ../../packages/serviceme-protocol/src/errors.ts
@@ -9497,8 +9496,22 @@ var SERVICEME_ERROR_CODES = [
9497
9496
  "opencode_request_failed",
9498
9497
  "opencode_backend_not_running",
9499
9498
  "opencode_session_not_found",
9499
+ "copilot_not_installed",
9500
+ "copilot_auth_required",
9501
+ "copilot_execution_failed",
9502
+ "copilot_timeout",
9500
9503
  "json_invalid_input",
9501
9504
  "env_tool_not_found",
9505
+ "task_not_found",
9506
+ "task_already_exists",
9507
+ "invalid_schedule",
9508
+ "invalid_payload",
9509
+ "confirmation_required",
9510
+ "workspace_not_found",
9511
+ "daemon_not_running",
9512
+ "executor_timeout",
9513
+ "executor_error",
9514
+ "task_already_running",
9502
9515
  "internal_error"
9503
9516
  ];
9504
9517
  var RETRYABLE_ERROR_CODES = /* @__PURE__ */ new Set([
@@ -9507,6 +9520,8 @@ var RETRYABLE_ERROR_CODES = /* @__PURE__ */ new Set([
9507
9520
  "opencode_startup_timeout",
9508
9521
  "opencode_request_failed",
9509
9522
  "opencode_backend_not_running",
9523
+ "copilot_timeout",
9524
+ "executor_timeout",
9510
9525
  "internal_error"
9511
9526
  ]);
9512
9527
  function isRecord2(value) {
@@ -9580,6 +9595,15 @@ function normalizeServicemeError(error, fallbackCode = "internal_error") {
9580
9595
  retryable: isRetryableErrorCode(fallbackCode)
9581
9596
  };
9582
9597
  }
9598
+
9599
+ // src/version.ts
9600
+ var SERVICEME_CLI_VERSION = "0.1.2";
9601
+
9602
+ // src/bridge/ndjson.ts
9603
+ function writeBridgeMessage(message) {
9604
+ process.stdout.write(`${JSON.stringify(message)}
9605
+ `);
9606
+ }
9583
9607
  function terminateCommandProcess(child) {
9584
9608
  if (child.killed) {
9585
9609
  return;
@@ -9673,12 +9697,14 @@ async function runCommand(command, options2 = {}) {
9673
9697
  }
9674
9698
  });
9675
9699
  }
9676
- async function commandExists(command) {
9677
- const isWindows = process.platform === "win32";
9700
+
9701
+ // ../../packages/serviceme-core/src/copilot/doctor.ts
9702
+ var GH_COMMAND = "gh";
9703
+ async function isCopilotAuthenticated() {
9678
9704
  try {
9679
- await runCommand(isWindows ? "where" : "which", {
9680
- args: [command],
9681
- timeoutMs: 5e3
9705
+ await runCommand(GH_COMMAND, {
9706
+ args: ["auth", "status"],
9707
+ timeoutMs: 1e4
9682
9708
  });
9683
9709
  return true;
9684
9710
  } catch {
@@ -9689,423 +9715,511 @@ async function commandExists(command) {
9689
9715
  // ../../packages/serviceme-core/src/json/jsonTools.ts
9690
9716
  __toESM(require_src2());
9691
9717
 
9692
- // ../../packages/serviceme-core/src/logger.ts
9693
- var noopLogger = {
9694
- debug() {
9695
- },
9696
- info() {
9697
- },
9698
- warn() {
9699
- },
9700
- error() {
9701
- }
9702
- };
9703
- var DEFAULT_OPTIONS = {
9704
- command: "opencode",
9705
- installUrl: "https://opencode.ai",
9706
- host: "127.0.0.1",
9707
- portMin: 16384,
9708
- portMax: 65535,
9709
- healthPath: "/app",
9710
- sessionPath: "/session",
9711
- sessionStatusPath: "/session/status",
9712
- startupPollIntervalMs: 200,
9713
- idleTimeoutMs: 10 * 60 * 1e3,
9714
- stdoutLogLimitBytes: 64 * 1024
9715
- };
9716
- var OpenCodeManager = class {
9717
- constructor(options2 = {}) {
9718
- this.stdoutBytes = 0;
9719
- this.options = {
9720
- ...DEFAULT_OPTIONS,
9721
- ...options2
9722
- };
9723
- this.logger = options2.logger ?? noopLogger;
9724
- }
9725
- async isAvailable() {
9726
- return commandExists(this.options.command);
9727
- }
9728
- isRunning() {
9729
- return Boolean(this.childProcess && !this.childProcess.killed && this.port);
9730
- }
9731
- async ensureServer(runtime = {}) {
9732
- await this.ensureServerStarted(runtime);
9733
- return this.getServerState();
9734
- }
9735
- async getServerState() {
9736
- const available = await this.isAvailable();
9737
- const running = this.isRunning();
9738
- const healthy = this.port ? await this.isServerHealthy(this.port) : false;
9739
- return {
9740
- available,
9741
- running,
9742
- healthy,
9743
- installUrl: this.options.installUrl,
9744
- pid: this.childProcess?.pid,
9745
- port: this.port
9746
- };
9747
- }
9748
- async createSession(runtime = {}) {
9749
- await this.ensureServerStarted(runtime);
9750
- const session = await this.request(
9751
- this.options.sessionPath,
9752
- {
9753
- method: "POST",
9754
- query: this.createQuery(runtime.workspaceRoot),
9755
- body: {}
9756
- }
9718
+ // ../../packages/serviceme-core/src/utils/fileUtils.ts
9719
+ __toESM(require_yauzl());
9720
+ var DEFAULT_TIMEOUT = 3e5;
9721
+ var MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
9722
+ var GithubCopilotCliExecutor = class {
9723
+ async execute(payload, abortSignal) {
9724
+ const authenticated = await isCopilotAuthenticated();
9725
+ if (!authenticated) {
9726
+ return {
9727
+ status: "failure",
9728
+ error: "GitHub Copilot CLI authentication failed. Please run `gh auth login` to re-authenticate."
9729
+ };
9730
+ }
9731
+ let output = "";
9732
+ const handle = this.executeStreaming(
9733
+ payload,
9734
+ (_stream, data) => {
9735
+ output += data;
9736
+ },
9737
+ abortSignal
9757
9738
  );
9758
- this.resetIdleTimer();
9759
- return {
9760
- sessionId: session.id,
9761
- title: session.title,
9762
- status: "running"
9763
- };
9739
+ const result = await handle.result;
9740
+ return { ...result, output: output || result.output };
9764
9741
  }
9765
- async prompt(input) {
9766
- await this.ensureServerStarted({ workspaceRoot: input.workspaceRoot });
9767
- await this.request(
9768
- `${this.options.sessionPath}/${input.sessionId}/message`,
9769
- {
9770
- method: "POST",
9771
- query: this.createQuery(input.workspaceRoot),
9772
- body: {
9773
- parts: [
9774
- {
9775
- type: "text",
9776
- text: input.prompt
9777
- }
9778
- ]
9779
- },
9780
- signal: input.signal
9781
- }
9782
- );
9783
- const latestMessage = await this.getLatestAssistantMessage(
9784
- input.sessionId,
9785
- input.workspaceRoot,
9786
- input.signal
9787
- );
9788
- input.onEvent?.({
9789
- type: "status",
9790
- status: "running"
9742
+ executeStreaming(payload, onOutput, abortSignal) {
9743
+ const p = payload;
9744
+ const timeout = (p.timeout != null ? p.timeout * 1e3 : null) ?? DEFAULT_TIMEOUT;
9745
+ let resolve;
9746
+ const resultPromise = new Promise((r) => {
9747
+ resolve = r;
9791
9748
  });
9792
- if (latestMessage) {
9793
- input.onEvent?.({
9794
- type: "message",
9795
- text: latestMessage
9796
- });
9749
+ let settled = false;
9750
+ const settle = (result) => {
9751
+ if (settled) return;
9752
+ settled = true;
9753
+ clearTimeout(timer);
9754
+ resolve?.(result);
9755
+ };
9756
+ if (abortSignal?.aborted) {
9757
+ return {
9758
+ result: Promise.resolve({
9759
+ status: "cancelled",
9760
+ error: "Execution aborted"
9761
+ }),
9762
+ cancel: () => {
9763
+ }
9764
+ };
9765
+ }
9766
+ const args = ["copilot", "prompt", "--prompt", p.prompt];
9767
+ if (p.autopilot) {
9768
+ args.push("--autopilot");
9769
+ }
9770
+ if (p.allowTools && p.allowTools.length > 0) {
9771
+ args.push("--allow-tools", p.allowTools.join(","));
9772
+ }
9773
+ if (p.model) {
9774
+ args.push("--model", p.model);
9797
9775
  }
9798
- input.onEvent?.({
9799
- type: "completed"
9776
+ if (p.agent) {
9777
+ args.push("--agent", p.agent);
9778
+ }
9779
+ if (p.timeout != null) {
9780
+ args.push("--timeout", String(p.timeout));
9781
+ }
9782
+ const child = child_process.spawn("serviceme", args, {
9783
+ cwd: p.workspace,
9784
+ stdio: ["ignore", "pipe", "pipe"]
9800
9785
  });
9801
- this.resetIdleTimer();
9802
- }
9803
- async getSessionStatus(sessionId, workspaceRoot) {
9804
- await this.ensureServerStarted({ workspaceRoot });
9805
- const response = await this.request(this.options.sessionStatusPath, {
9806
- method: "GET",
9807
- query: this.createQuery(workspaceRoot)
9786
+ const timer = setTimeout(() => {
9787
+ child.kill("SIGTERM");
9788
+ setTimeout(() => {
9789
+ if (!child.killed) child.kill("SIGKILL");
9790
+ }, 5e3);
9791
+ settle({
9792
+ status: "timeout",
9793
+ error: `Copilot CLI execution timed out after ${timeout / 1e3}s`
9794
+ });
9795
+ }, timeout);
9796
+ let stdoutBuf = "";
9797
+ let stderrBuf = "";
9798
+ child.stdout.on("data", (chunk) => {
9799
+ const data = chunk.toString();
9800
+ stdoutBuf += data;
9801
+ if (stdoutBuf.length > MAX_OUTPUT_BYTES)
9802
+ stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES);
9803
+ onOutput("stdout", data);
9808
9804
  });
9809
- const session = response.find((item) => item.id === sessionId);
9810
- this.resetIdleTimer();
9811
- return this.mapStatus(session?.status);
9812
- }
9813
- async abortSession(sessionId, workspaceRoot) {
9814
- await this.ensureServerStarted({ workspaceRoot });
9815
- await this.request(`${this.options.sessionPath}/${sessionId}/abort`, {
9816
- method: "POST",
9817
- query: this.createQuery(workspaceRoot)
9805
+ child.stderr.on("data", (chunk) => {
9806
+ const data = chunk.toString();
9807
+ stderrBuf += data;
9808
+ if (stderrBuf.length > MAX_OUTPUT_BYTES)
9809
+ stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES);
9810
+ onOutput("stderr", data);
9818
9811
  });
9819
- this.resetIdleTimer();
9812
+ child.on("close", (code) => {
9813
+ if (code === 0) {
9814
+ settle({ status: "success", output: stdoutBuf || void 0 });
9815
+ } else {
9816
+ settle({
9817
+ status: "failure",
9818
+ output: stdoutBuf || void 0,
9819
+ error: stderrBuf || `Process exited with code ${code}`
9820
+ });
9821
+ }
9822
+ });
9823
+ child.on("error", (err) => {
9824
+ settle({ status: "failure", error: err.message });
9825
+ });
9826
+ const cancelFn = () => {
9827
+ child.kill("SIGTERM");
9828
+ setTimeout(() => {
9829
+ if (!child.killed) child.kill("SIGKILL");
9830
+ }, 5e3);
9831
+ settle({ status: "cancelled", error: "Execution cancelled" });
9832
+ };
9833
+ abortSignal?.addEventListener("abort", () => cancelFn(), { once: true });
9834
+ return { result: resultPromise, cancel: cancelFn };
9820
9835
  }
9821
- async dispose() {
9822
- if (this.idleTimer) {
9823
- clearTimeout(this.idleTimer);
9824
- this.idleTimer = void 0;
9836
+ };
9837
+
9838
+ // ../../packages/serviceme-core/src/scheduled-tasks/executors/HttpRequestExecutor.ts
9839
+ var DEFAULT_TIMEOUT2 = 3e4;
9840
+ var HttpRequestExecutor = class {
9841
+ async execute(payload, abortSignal) {
9842
+ const p = payload;
9843
+ const timeout = (p.timeout != null ? p.timeout * 1e3 : null) ?? DEFAULT_TIMEOUT2;
9844
+ const ac = new AbortController();
9845
+ const timer = setTimeout(() => {
9846
+ ac.abort();
9847
+ }, timeout);
9848
+ if (abortSignal?.aborted) {
9849
+ clearTimeout(timer);
9850
+ return { status: "cancelled", error: "Execution aborted" };
9825
9851
  }
9826
- await this.terminateTrackedProcess("dispose");
9827
- this.startupPromise = void 0;
9828
- }
9829
- async ensureServerStarted(runtime) {
9830
- if (this.port) {
9831
- if (await this.isServerHealthy(this.port)) {
9832
- return;
9852
+ let externalAbort = false;
9853
+ abortSignal?.addEventListener(
9854
+ "abort",
9855
+ () => {
9856
+ externalAbort = true;
9857
+ clearTimeout(timer);
9858
+ ac.abort();
9859
+ },
9860
+ { once: true }
9861
+ );
9862
+ try {
9863
+ const response = await fetch(p.url, {
9864
+ method: p.method,
9865
+ headers: p.headers,
9866
+ body: p.body,
9867
+ signal: ac.signal
9868
+ });
9869
+ clearTimeout(timer);
9870
+ const body = await response.text();
9871
+ if (response.ok) {
9872
+ return {
9873
+ status: "success",
9874
+ output: `${response.status} ${response.statusText}
9875
+ ${body}`.trim()
9876
+ };
9833
9877
  }
9834
- await this.terminateTrackedProcess("restart-unhealthy");
9835
- }
9836
- if (this.startupPromise) {
9837
- await this.startupPromise;
9838
- return;
9839
- }
9840
- if (!await this.isAvailable()) {
9841
- throw createServicemeError(
9842
- "opencode_not_installed",
9843
- "OpenCode CLI is not installed."
9844
- );
9845
- }
9846
- const MAX_PORT_RETRIES = 3;
9847
- let lastError;
9848
- for (let attempt = 0; attempt < MAX_PORT_RETRIES; attempt++) {
9849
- this.startupPromise = this.startServerProcess(runtime);
9850
- try {
9851
- await this.startupPromise;
9852
- return;
9853
- } catch (error) {
9854
- lastError = error;
9855
- this.logger.warn(
9856
- `OpenCode server start attempt ${String(attempt + 1)} failed, retrying...`
9857
- );
9858
- } finally {
9859
- this.startupPromise = void 0;
9878
+ return {
9879
+ status: "failure",
9880
+ error: `HTTP ${response.status} ${response.statusText}
9881
+ ${body}`.trim()
9882
+ };
9883
+ } catch (err) {
9884
+ clearTimeout(timer);
9885
+ if (err instanceof Error && err.name === "AbortError") {
9886
+ if (externalAbort) {
9887
+ return { status: "cancelled", error: "Execution cancelled" };
9888
+ }
9889
+ return {
9890
+ status: "timeout",
9891
+ error: `HTTP request timed out after ${timeout / 1e3}s`
9892
+ };
9860
9893
  }
9894
+ const message = err instanceof Error ? err.message : String(err);
9895
+ return { status: "failure", error: message };
9861
9896
  }
9862
- throw lastError;
9863
9897
  }
9864
- async startServerProcess(runtime) {
9865
- const port = crypto.randomInt(this.options.portMin, this.options.portMax + 1);
9866
- const child = child_process.spawn(this.options.command, ["--port", String(port)], {
9867
- cwd: runtime.workspaceRoot,
9868
- detached: process.platform !== "win32",
9869
- env: {
9870
- ...process.env,
9871
- OPENCODE_CALLER: "serviceme-cli"
9898
+ };
9899
+ var DEFAULT_TIMEOUT3 = 6e4;
9900
+ var MAX_OUTPUT_BYTES2 = 1024 * 1024;
9901
+ var ShellExecutor = class {
9902
+ async execute(payload, abortSignal) {
9903
+ let output = "";
9904
+ const handle = this.executeStreaming(
9905
+ payload,
9906
+ (_stream, data) => {
9907
+ output += data;
9872
9908
  },
9909
+ abortSignal
9910
+ );
9911
+ const result = await handle.result;
9912
+ return { ...result, output: output || result.output };
9913
+ }
9914
+ executeStreaming(payload, onOutput, abortSignal) {
9915
+ const p = payload;
9916
+ const timeout = (p.timeout != null ? p.timeout * 1e3 : null) ?? DEFAULT_TIMEOUT3;
9917
+ let resolve;
9918
+ const resultPromise = new Promise((r) => {
9919
+ resolve = r;
9920
+ });
9921
+ let settled = false;
9922
+ const settle = (result) => {
9923
+ if (settled) return;
9924
+ settled = true;
9925
+ clearTimeout(timer);
9926
+ resolve?.(result);
9927
+ };
9928
+ if (abortSignal?.aborted) {
9929
+ return {
9930
+ result: Promise.resolve({
9931
+ status: "cancelled",
9932
+ error: "Execution aborted"
9933
+ }),
9934
+ cancel: () => {
9935
+ }
9936
+ };
9937
+ }
9938
+ const child = child_process.spawn("sh", ["-c", p.script], {
9939
+ cwd: p.cwd,
9873
9940
  stdio: ["ignore", "pipe", "pipe"]
9874
9941
  });
9875
- this.stdoutBytes = 0;
9876
- child.stdout?.on("data", (chunk) => {
9877
- if (this.stdoutBytes < this.options.stdoutLogLimitBytes) {
9878
- this.logger.debug("OpenCode stdout", chunk.toString());
9879
- this.stdoutBytes += chunk.length;
9880
- }
9942
+ const timer = setTimeout(() => {
9943
+ child.kill("SIGTERM");
9944
+ setTimeout(() => {
9945
+ if (!child.killed) child.kill("SIGKILL");
9946
+ }, 5e3);
9947
+ settle({
9948
+ status: "timeout",
9949
+ error: `Shell execution timed out after ${timeout / 1e3}s`
9950
+ });
9951
+ }, timeout);
9952
+ let stdoutBuf = "";
9953
+ let stderrBuf = "";
9954
+ child.stdout.on("data", (chunk) => {
9955
+ const data = chunk.toString();
9956
+ stdoutBuf += data;
9957
+ if (stdoutBuf.length > MAX_OUTPUT_BYTES2)
9958
+ stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES2);
9959
+ onOutput("stdout", data);
9881
9960
  });
9882
- child.stderr?.on("data", (chunk) => {
9883
- this.logger.warn("OpenCode stderr", chunk.toString());
9961
+ child.stderr.on("data", (chunk) => {
9962
+ const data = chunk.toString();
9963
+ stderrBuf += data;
9964
+ if (stderrBuf.length > MAX_OUTPUT_BYTES2)
9965
+ stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES2);
9966
+ onOutput("stderr", data);
9884
9967
  });
9885
- child.on("error", (error) => {
9886
- this.logger.error("OpenCode process error", error);
9887
- if (this.childProcess === child) {
9888
- this.childProcess = void 0;
9889
- this.port = void 0;
9968
+ child.on("close", (code) => {
9969
+ if (code === 0) {
9970
+ settle({ status: "success", output: stdoutBuf || void 0 });
9971
+ } else {
9972
+ settle({
9973
+ status: "failure",
9974
+ output: stdoutBuf || void 0,
9975
+ error: stderrBuf || `Process exited with code ${code}`
9976
+ });
9890
9977
  }
9891
9978
  });
9892
- child.on("exit", () => {
9893
- if (this.childProcess === child) {
9894
- this.childProcess = void 0;
9895
- this.port = void 0;
9896
- }
9979
+ child.on("error", (err) => {
9980
+ settle({ status: "failure", error: err.message });
9897
9981
  });
9898
- if (process.platform !== "win32" && child.pid) {
9899
- child_process.spawn("renice", ["+10", String(child.pid)], { stdio: "ignore" }).unref();
9900
- }
9901
- this.childProcess = child;
9902
- this.port = port;
9903
- const ready = await this.waitForHealth(
9904
- port,
9905
- runtime.startupTimeoutMs ?? 5e3
9906
- );
9907
- if (!ready) {
9908
- await this.terminateTrackedProcess("startup-timeout");
9909
- throw createServicemeError(
9910
- "opencode_startup_timeout",
9911
- "OpenCode local server did not start in time."
9912
- );
9913
- }
9982
+ const cancelFn = () => {
9983
+ child.kill("SIGTERM");
9984
+ setTimeout(() => {
9985
+ if (!child.killed) child.kill("SIGKILL");
9986
+ }, 5e3);
9987
+ settle({ status: "cancelled", error: "Execution cancelled" });
9988
+ };
9989
+ abortSignal?.addEventListener("abort", () => cancelFn(), { once: true });
9990
+ return { result: resultPromise, cancel: cancelFn };
9914
9991
  }
9915
- async waitForHealth(port, timeoutMs) {
9916
- const startedAt = Date.now();
9917
- while (Date.now() - startedAt < timeoutMs) {
9918
- if (await this.isServerHealthy(port)) {
9919
- return true;
9920
- }
9921
- await new Promise((resolve) => {
9922
- setTimeout(resolve, this.options.startupPollIntervalMs);
9923
- });
9924
- }
9925
- return false;
9992
+ };
9993
+
9994
+ // ../../packages/serviceme-core/src/scheduled-tasks/executors/types.ts
9995
+ function isStreamingTaskExecutor(executor) {
9996
+ return "executeStreaming" in executor && typeof executor.executeStreaming === "function";
9997
+ }
9998
+
9999
+ // ../../packages/serviceme-core/src/scheduled-tasks/executors/index.ts
10000
+ var executors = {
10001
+ shell: new ShellExecutor(),
10002
+ http_request: new HttpRequestExecutor(),
10003
+ github_copilot_cli: new GithubCopilotCliExecutor()
10004
+ };
10005
+ function getExecutor(taskType) {
10006
+ return executors[taskType];
10007
+ }
10008
+
10009
+ // ../../packages/serviceme-core/src/scheduled-tasks/TaskExecutionEngine.ts
10010
+ var TaskExecutionEngine = class {
10011
+ constructor(getExecutor2) {
10012
+ this.getExecutor = getExecutor2;
10013
+ this.running = /* @__PURE__ */ new Map();
10014
+ this.taskExecutions = /* @__PURE__ */ new Map();
10015
+ this.listener = null;
9926
10016
  }
9927
- async isServerHealthy(port) {
9928
- try {
9929
- const response = await fetch(
9930
- `http://${this.options.host}:${port}${this.options.healthPath}`
9931
- );
9932
- return response.ok;
9933
- } catch {
9934
- return false;
9935
- }
10017
+ setListener(listener) {
10018
+ this.listener = listener;
9936
10019
  }
9937
- resetIdleTimer() {
9938
- if (this.idleTimer) {
9939
- clearTimeout(this.idleTimer);
10020
+ async execute(snapshot) {
10021
+ const policy = snapshot.concurrencyPolicy ?? "reject";
10022
+ if (policy === "reject") {
10023
+ const existingIds = this.taskExecutions.get(snapshot.taskId);
10024
+ if (existingIds && existingIds.size > 0) {
10025
+ throw new Error(
10026
+ `TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`
10027
+ );
10028
+ }
9940
10029
  }
9941
- this.idleTimer = setTimeout(() => {
9942
- void this.dispose();
9943
- }, this.options.idleTimeoutMs);
9944
- this.idleTimer.unref?.();
9945
- }
9946
- async terminateTrackedProcess(reason) {
9947
- const child = this.childProcess;
9948
- this.childProcess = void 0;
9949
- this.port = void 0;
9950
- if (!child || child.killed) {
9951
- return;
10030
+ const executor = this.getExecutor(snapshot.type);
10031
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
10032
+ if (!this.taskExecutions.has(snapshot.taskId)) {
10033
+ this.taskExecutions.set(snapshot.taskId, /* @__PURE__ */ new Set());
9952
10034
  }
9953
- this.logger.info("Stopping tracked OpenCode process", {
9954
- reason,
9955
- pid: child.pid
10035
+ this.taskExecutions.get(snapshot.taskId)?.add(snapshot.executionId);
10036
+ const ac = new AbortController();
10037
+ const runningExec = {
10038
+ executionId: snapshot.executionId,
10039
+ taskId: snapshot.taskId,
10040
+ startedAt,
10041
+ cancel: () => ac.abort()
10042
+ };
10043
+ this.running.set(snapshot.executionId, runningExec);
10044
+ this.listener?.onStarted({
10045
+ executionId: snapshot.executionId,
10046
+ taskId: snapshot.taskId,
10047
+ startedAt
9956
10048
  });
9957
- if (process.platform === "win32") {
9958
- if (child.pid) {
9959
- await new Promise((resolve) => {
9960
- const killProcess = child_process.spawn(
9961
- "taskkill",
9962
- ["/pid", String(child.pid), "/T", "/F"],
9963
- {
9964
- stdio: "ignore",
9965
- windowsHide: true
9966
- }
9967
- );
9968
- killProcess.once("exit", () => resolve());
9969
- killProcess.once("error", () => {
9970
- child.kill();
9971
- resolve();
10049
+ try {
10050
+ let result;
10051
+ if (isStreamingTaskExecutor(executor)) {
10052
+ const handle = executor.executeStreaming(
10053
+ snapshot.payload,
10054
+ (stream, data) => {
10055
+ this.listener?.onOutput({
10056
+ executionId: snapshot.executionId,
10057
+ stream,
10058
+ data
10059
+ });
10060
+ },
10061
+ ac.signal
10062
+ );
10063
+ runningExec.cancel = () => {
10064
+ handle.cancel();
10065
+ ac.abort();
10066
+ };
10067
+ result = await handle.result;
10068
+ } else {
10069
+ result = await executor.execute(snapshot.payload, ac.signal);
10070
+ }
10071
+ const log = {
10072
+ id: snapshot.executionId,
10073
+ taskId: snapshot.taskId,
10074
+ taskName: snapshot.name,
10075
+ startedAt,
10076
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
10077
+ status: result.status === "running" ? "failure" : result.status,
10078
+ output: result.output,
10079
+ error: result.error
10080
+ };
10081
+ switch (log.status) {
10082
+ case "success":
10083
+ this.listener?.onCompleted({
10084
+ executionId: snapshot.executionId,
10085
+ log
9972
10086
  });
10087
+ break;
10088
+ case "cancelled":
10089
+ this.listener?.onCancelled({
10090
+ executionId: snapshot.executionId,
10091
+ log
10092
+ });
10093
+ break;
10094
+ case "timeout":
10095
+ this.listener?.onFailed({
10096
+ executionId: snapshot.executionId,
10097
+ status: "timeout",
10098
+ reason: "timeout",
10099
+ log
10100
+ });
10101
+ break;
10102
+ default:
10103
+ this.listener?.onFailed({
10104
+ executionId: snapshot.executionId,
10105
+ status: "failure",
10106
+ reason: "error",
10107
+ log
10108
+ });
10109
+ break;
10110
+ }
10111
+ } catch (err) {
10112
+ const log = {
10113
+ id: snapshot.executionId,
10114
+ taskId: snapshot.taskId,
10115
+ taskName: snapshot.name,
10116
+ startedAt,
10117
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
10118
+ status: ac.signal.aborted ? "cancelled" : "failure",
10119
+ error: err instanceof Error ? err.message : String(err)
10120
+ };
10121
+ if (ac.signal.aborted) {
10122
+ this.listener?.onCancelled({
10123
+ executionId: snapshot.executionId,
10124
+ log
10125
+ });
10126
+ } else {
10127
+ this.listener?.onFailed({
10128
+ executionId: snapshot.executionId,
10129
+ status: "failure",
10130
+ reason: "error",
10131
+ log
9973
10132
  });
9974
- return;
9975
10133
  }
9976
- child.kill();
9977
- return;
9978
- }
9979
- if (!child.pid) {
9980
- child.kill("SIGTERM");
9981
- return;
10134
+ } finally {
10135
+ this.running.delete(snapshot.executionId);
10136
+ const taskExecIds = this.taskExecutions.get(snapshot.taskId);
10137
+ if (taskExecIds) {
10138
+ taskExecIds.delete(snapshot.executionId);
10139
+ if (taskExecIds.size === 0) {
10140
+ this.taskExecutions.delete(snapshot.taskId);
10141
+ }
10142
+ }
9982
10143
  }
9983
- try {
9984
- process.kill(-child.pid, "SIGTERM");
9985
- } catch {
9986
- child.kill("SIGTERM");
9987
- return;
10144
+ }
10145
+ cancel(executionId) {
10146
+ const exec = this.running.get(executionId);
10147
+ if (!exec) return false;
10148
+ exec.cancel();
10149
+ return true;
10150
+ }
10151
+ listRunning() {
10152
+ return Array.from(this.running.values()).map((e) => ({
10153
+ executionId: e.executionId,
10154
+ taskId: e.taskId,
10155
+ startedAt: e.startedAt
10156
+ }));
10157
+ }
10158
+ dispose() {
10159
+ for (const exec of this.running.values()) {
10160
+ exec.cancel();
9988
10161
  }
9989
- const childPid = child.pid;
9990
- await new Promise((resolve) => {
9991
- const escalationTimer = setTimeout(() => {
9992
- try {
9993
- process.kill(-childPid, "SIGKILL");
9994
- } catch {
9995
- }
9996
- resolve();
9997
- }, 2e3);
9998
- child.once("exit", () => {
9999
- clearTimeout(escalationTimer);
10000
- try {
10001
- process.kill(-childPid, "SIGKILL");
10002
- } catch {
10003
- }
10004
- resolve();
10005
- });
10162
+ this.running.clear();
10163
+ this.taskExecutions.clear();
10164
+ }
10165
+ };
10166
+
10167
+ // src/bridge/TaskBridgeHandler.ts
10168
+ var TaskBridgeHandler = class {
10169
+ constructor(logger, emitEvent) {
10170
+ this.logger = logger;
10171
+ this.emitEvent = emitEvent;
10172
+ this.engine = new TaskExecutionEngine(
10173
+ (taskType) => getExecutor(taskType)
10174
+ );
10175
+ this.engine.setListener({
10176
+ onStarted: (params) => this.emitEvent("task.started", params),
10177
+ onOutput: (params) => this.emitEvent("task.output", params),
10178
+ onCompleted: (params) => this.emitEvent("task.completed", params),
10179
+ onFailed: (params) => this.emitEvent("task.failed", params),
10180
+ onCancelled: (params) => this.emitEvent("task.cancelled", params)
10006
10181
  });
10007
10182
  }
10008
- async request(path3, options2) {
10009
- if (!this.port) {
10010
- throw createServicemeError(
10011
- "opencode_backend_not_running",
10012
- "OpenCode local server is not running."
10013
- );
10014
- }
10015
- const url = new URL(`http://${this.options.host}:${this.port}${path3}`);
10016
- for (const [key, value] of Object.entries(options2.query ?? {})) {
10017
- if (value) {
10018
- url.searchParams.set(key, value);
10019
- }
10020
- }
10021
- const response = await fetch(url, {
10022
- method: options2.method,
10023
- headers: {
10024
- "Content-Type": "application/json"
10025
- },
10026
- body: options2.body ? JSON.stringify(options2.body) : void 0,
10027
- signal: options2.signal
10183
+ async execute(snapshot) {
10184
+ let earlyError;
10185
+ const executePromise = this.engine.execute(snapshot).catch((err) => {
10186
+ earlyError = err;
10028
10187
  });
10029
- if (!response.ok) {
10030
- throw createServicemeError(
10031
- "opencode_request_failed",
10032
- `OpenCode request failed: ${response.status} ${response.statusText}`
10033
- );
10188
+ await Promise.resolve();
10189
+ if (earlyError) {
10190
+ throw earlyError;
10034
10191
  }
10035
- if (response.status === 204) {
10036
- return void 0;
10037
- }
10038
- return await response.json();
10192
+ executePromise.then(void 0, (err) => {
10193
+ this.logger.error("Task execution background error", err);
10194
+ });
10195
+ return { executionId: snapshot.executionId, accepted: true };
10039
10196
  }
10040
- createQuery(workspaceRoot) {
10041
- return {
10042
- directory: workspaceRoot
10043
- };
10197
+ cancel(params) {
10198
+ const cancelled = this.engine.cancel(params.executionId);
10199
+ return { executionId: params.executionId, cancelled };
10044
10200
  }
10045
- async getLatestAssistantMessage(sessionId, workspaceRoot, signal) {
10046
- const messages = await this.request(
10047
- `${this.options.sessionPath}/${sessionId}/message`,
10048
- {
10049
- method: "GET",
10050
- query: {
10051
- ...this.createQuery(workspaceRoot),
10052
- limit: "20"
10053
- },
10054
- signal
10055
- }
10056
- );
10057
- const latestAssistant = [...messages].reverse().find((message) => message.info?.role === "assistant");
10058
- if (!latestAssistant?.parts) {
10059
- return void 0;
10060
- }
10061
- return latestAssistant.parts.filter(
10062
- (part) => part.type === "text" && typeof part.text === "string"
10063
- ).map((part) => part.text).join("\n\n");
10201
+ listRunning() {
10202
+ return { executions: this.engine.listRunning() };
10064
10203
  }
10065
- mapStatus(status) {
10066
- switch (status) {
10067
- case "running":
10068
- case "active":
10069
- return "running";
10070
- case "needs_input":
10071
- return "needs-input";
10072
- case "completed":
10073
- return "completed";
10074
- case "failed":
10075
- return "failed";
10076
- case "aborted":
10077
- return "aborted";
10078
- default:
10079
- return "idle";
10080
- }
10204
+ dispose() {
10205
+ this.engine.dispose();
10081
10206
  }
10082
10207
  };
10083
10208
 
10084
- // ../../packages/serviceme-core/src/utils/fileUtils.ts
10085
- __toESM(require_yauzl());
10086
-
10087
- // src/version.ts
10088
- var SERVICEME_CLI_VERSION = "0.0.6";
10089
-
10090
- // src/bridge/ndjson.ts
10091
- function writeBridgeMessage(message) {
10092
- process.stdout.write(`${JSON.stringify(message)}
10093
- `);
10094
- }
10095
-
10096
10209
  // src/bridge/BridgeServer.ts
10097
10210
  var CAPABILITIES = {
10098
10211
  bridge: true,
10099
- opencode: 1,
10100
10212
  json: 1,
10101
- env: 1
10213
+ env: 1,
10214
+ tasks: 1
10102
10215
  };
10103
10216
  var BridgeServer = class {
10104
- constructor(logger, options2 = {}) {
10217
+ constructor(logger) {
10105
10218
  this.logger = logger;
10106
10219
  this.handshakeCompleted = false;
10107
- this.shuttingDown = false;
10108
- this.openCodeManager = options2.openCodeManager ?? new OpenCodeManager({ logger });
10220
+ this.taskHandler = new TaskBridgeHandler(logger, (event, params) => {
10221
+ this.writeEvent(event, params);
10222
+ });
10109
10223
  }
10110
10224
  async run() {
10111
10225
  const reader = readline__namespace.createInterface({
@@ -10117,7 +10231,7 @@ var BridgeServer = class {
10117
10231
  void this.handleLine(line);
10118
10232
  });
10119
10233
  reader.on("close", () => {
10120
- void this.dispose().finally(resolve);
10234
+ resolve();
10121
10235
  });
10122
10236
  });
10123
10237
  }
@@ -10173,111 +10287,42 @@ var BridgeServer = class {
10173
10287
  }
10174
10288
  case "system.shutdown": {
10175
10289
  const shutdownRequest = request;
10290
+ this.taskHandler.dispose();
10176
10291
  this.writeSuccess(shutdownRequest.id, {
10177
10292
  shuttingDown: true
10178
10293
  });
10179
10294
  queueMicrotask(() => {
10180
- void this.dispose().finally(() => {
10181
- process.exit(0);
10182
- });
10295
+ process.exit(0);
10183
10296
  });
10184
10297
  return;
10185
10298
  }
10186
- case "opencode.server.ensure": {
10187
- const ensureRequest = request;
10188
- const result = await this.openCodeManager.ensureServer(
10189
- ensureRequest.params
10190
- );
10191
- this.writeSuccess(ensureRequest.id, result);
10192
- return;
10193
- }
10194
- case "opencode.server.status": {
10195
- const statusRequest = request;
10196
- const result = await this.openCodeManager.getServerState();
10197
- this.writeSuccess(statusRequest.id, result);
10198
- return;
10199
- }
10200
- case "opencode.session.create": {
10201
- const createRequest = request;
10202
- const result = await this.openCodeManager.createSession({
10203
- workspaceRoot: createRequest.params.workspaceRoot,
10204
- startupTimeoutMs: createRequest.params.runtime?.startupTimeoutMs
10205
- });
10206
- this.writeSuccess(createRequest.id, result);
10207
- return;
10208
- }
10209
- case "opencode.session.prompt": {
10210
- const promptRequest = request;
10211
- this.writeSuccess(promptRequest.id, {
10212
- accepted: true,
10213
- sessionId: promptRequest.params.sessionId,
10214
- promptId: promptRequest.params.promptId
10215
- });
10216
- void this.openCodeManager.prompt({
10217
- sessionId: promptRequest.params.sessionId,
10218
- prompt: promptRequest.params.prompt,
10219
- workspaceRoot: promptRequest.params.workspaceRoot,
10220
- onEvent: (payload) => {
10221
- this.writeEvent("opencode.session.event", {
10222
- sessionId: promptRequest.params.sessionId,
10223
- promptId: promptRequest.params.promptId,
10224
- payload
10225
- });
10299
+ case "task.execute": {
10300
+ const execRequest = request;
10301
+ try {
10302
+ const result = await this.taskHandler.execute(execRequest.params);
10303
+ this.writeSuccess(request.id, result);
10304
+ } catch (err) {
10305
+ const message = err instanceof Error ? err.message : String(err);
10306
+ if (message.includes("TASK_ALREADY_RUNNING")) {
10307
+ this.writeError(
10308
+ request.id,
10309
+ createServicemeError("task_already_running", message)
10310
+ );
10311
+ } else {
10312
+ this.writeError(request.id, err);
10226
10313
  }
10227
- }).catch((error) => {
10228
- const normalized = normalizeServicemeError(error);
10229
- this.writeEvent("opencode.session.event", {
10230
- sessionId: promptRequest.params.sessionId,
10231
- promptId: promptRequest.params.promptId,
10232
- payload: {
10233
- type: "error",
10234
- text: normalized.message,
10235
- metadata: {
10236
- code: normalized.code,
10237
- retryable: normalized.retryable
10238
- }
10239
- }
10240
- });
10241
- });
10314
+ }
10242
10315
  return;
10243
10316
  }
10244
- case "opencode.session.status": {
10245
- const statusRequest = request;
10246
- const status = await this.openCodeManager.getSessionStatus(
10247
- statusRequest.params.sessionId,
10248
- statusRequest.params.workspaceRoot
10249
- );
10250
- this.writeSuccess(statusRequest.id, {
10251
- sessionId: statusRequest.params.sessionId,
10252
- status
10253
- });
10317
+ case "task.cancel": {
10318
+ const cancelRequest = request;
10319
+ const result = this.taskHandler.cancel(cancelRequest.params);
10320
+ this.writeSuccess(request.id, result);
10254
10321
  return;
10255
10322
  }
10256
- case "opencode.session.abort": {
10257
- const abortRequest = request;
10258
- await this.openCodeManager.abortSession(
10259
- abortRequest.params.sessionId,
10260
- abortRequest.params.workspaceRoot
10261
- );
10262
- this.writeSuccess(abortRequest.id, {
10263
- aborted: true,
10264
- sessionId: abortRequest.params.sessionId
10265
- });
10266
- this.writeEvent("opencode.session.event", {
10267
- sessionId: abortRequest.params.sessionId,
10268
- payload: {
10269
- type: "status",
10270
- status: "aborted"
10271
- }
10272
- });
10273
- return;
10274
- }
10275
- case "opencode.session.dispose": {
10276
- const disposeRequest = request;
10277
- this.writeSuccess(disposeRequest.id, {
10278
- disposed: true,
10279
- sessionId: disposeRequest.params.sessionId
10280
- });
10323
+ case "task.list-running": {
10324
+ const result = this.taskHandler.listRunning();
10325
+ this.writeSuccess(request.id, result);
10281
10326
  return;
10282
10327
  }
10283
10328
  default:
@@ -10319,13 +10364,6 @@ var BridgeServer = class {
10319
10364
  params
10320
10365
  });
10321
10366
  }
10322
- async dispose() {
10323
- if (this.shuttingDown) {
10324
- return;
10325
- }
10326
- this.shuttingDown = true;
10327
- await this.openCodeManager.dispose();
10328
- }
10329
10367
  };
10330
10368
 
10331
10369
  exports.BridgeServer = BridgeServer;