@serviceme/devtools-cli 0.0.6 → 0.1.3

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 +542 -500
  2. package/dist/cli.js +13554 -11773
  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.3";
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,515 @@ 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
+ const executor = executors[taskType];
10007
+ if (!executor) {
10008
+ throw new Error(`Unsupported bridge task type: ${taskType}`);
9926
10009
  }
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
- }
10010
+ return executor;
10011
+ }
10012
+
10013
+ // ../../packages/serviceme-core/src/scheduled-tasks/TaskExecutionEngine.ts
10014
+ var TaskExecutionEngine = class {
10015
+ constructor(getExecutor2) {
10016
+ this.getExecutor = getExecutor2;
10017
+ this.running = /* @__PURE__ */ new Map();
10018
+ this.taskExecutions = /* @__PURE__ */ new Map();
10019
+ this.listener = null;
9936
10020
  }
9937
- resetIdleTimer() {
9938
- if (this.idleTimer) {
9939
- clearTimeout(this.idleTimer);
9940
- }
9941
- this.idleTimer = setTimeout(() => {
9942
- void this.dispose();
9943
- }, this.options.idleTimeoutMs);
9944
- this.idleTimer.unref?.();
10021
+ setListener(listener) {
10022
+ this.listener = listener;
9945
10023
  }
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;
10024
+ async execute(snapshot) {
10025
+ const policy = snapshot.concurrencyPolicy ?? "reject";
10026
+ if (policy === "reject") {
10027
+ const existingIds = this.taskExecutions.get(snapshot.taskId);
10028
+ if (existingIds && existingIds.size > 0) {
10029
+ throw new Error(
10030
+ `TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`
10031
+ );
10032
+ }
9952
10033
  }
9953
- this.logger.info("Stopping tracked OpenCode process", {
9954
- reason,
9955
- pid: child.pid
10034
+ const executor = this.getExecutor(snapshot.type);
10035
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
10036
+ if (!this.taskExecutions.has(snapshot.taskId)) {
10037
+ this.taskExecutions.set(snapshot.taskId, /* @__PURE__ */ new Set());
10038
+ }
10039
+ this.taskExecutions.get(snapshot.taskId)?.add(snapshot.executionId);
10040
+ const ac = new AbortController();
10041
+ const runningExec = {
10042
+ executionId: snapshot.executionId,
10043
+ taskId: snapshot.taskId,
10044
+ startedAt,
10045
+ cancel: () => ac.abort()
10046
+ };
10047
+ this.running.set(snapshot.executionId, runningExec);
10048
+ this.listener?.onStarted({
10049
+ executionId: snapshot.executionId,
10050
+ taskId: snapshot.taskId,
10051
+ startedAt
9956
10052
  });
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();
10053
+ try {
10054
+ let result;
10055
+ if (isStreamingTaskExecutor(executor)) {
10056
+ const handle = executor.executeStreaming(
10057
+ snapshot.payload,
10058
+ (stream, data) => {
10059
+ this.listener?.onOutput({
10060
+ executionId: snapshot.executionId,
10061
+ stream,
10062
+ data
10063
+ });
10064
+ },
10065
+ ac.signal
10066
+ );
10067
+ runningExec.cancel = () => {
10068
+ handle.cancel();
10069
+ ac.abort();
10070
+ };
10071
+ result = await handle.result;
10072
+ } else {
10073
+ result = await executor.execute(snapshot.payload, ac.signal);
10074
+ }
10075
+ const log = {
10076
+ id: snapshot.executionId,
10077
+ taskId: snapshot.taskId,
10078
+ taskName: snapshot.name,
10079
+ startedAt,
10080
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
10081
+ status: result.status === "running" ? "failure" : result.status,
10082
+ output: result.output,
10083
+ error: result.error
10084
+ };
10085
+ switch (log.status) {
10086
+ case "success":
10087
+ this.listener?.onCompleted({
10088
+ executionId: snapshot.executionId,
10089
+ log
10090
+ });
10091
+ break;
10092
+ case "cancelled":
10093
+ this.listener?.onCancelled({
10094
+ executionId: snapshot.executionId,
10095
+ log
10096
+ });
10097
+ break;
10098
+ case "timeout":
10099
+ this.listener?.onFailed({
10100
+ executionId: snapshot.executionId,
10101
+ status: "timeout",
10102
+ reason: "timeout",
10103
+ log
10104
+ });
10105
+ break;
10106
+ default:
10107
+ this.listener?.onFailed({
10108
+ executionId: snapshot.executionId,
10109
+ status: "failure",
10110
+ reason: "error",
10111
+ log
9972
10112
  });
10113
+ break;
10114
+ }
10115
+ } catch (err) {
10116
+ const log = {
10117
+ id: snapshot.executionId,
10118
+ taskId: snapshot.taskId,
10119
+ taskName: snapshot.name,
10120
+ startedAt,
10121
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
10122
+ status: ac.signal.aborted ? "cancelled" : "failure",
10123
+ error: err instanceof Error ? err.message : String(err)
10124
+ };
10125
+ if (ac.signal.aborted) {
10126
+ this.listener?.onCancelled({
10127
+ executionId: snapshot.executionId,
10128
+ log
10129
+ });
10130
+ } else {
10131
+ this.listener?.onFailed({
10132
+ executionId: snapshot.executionId,
10133
+ status: "failure",
10134
+ reason: "error",
10135
+ log
9973
10136
  });
9974
- return;
9975
10137
  }
9976
- child.kill();
9977
- return;
9978
- }
9979
- if (!child.pid) {
9980
- child.kill("SIGTERM");
9981
- return;
10138
+ } finally {
10139
+ this.running.delete(snapshot.executionId);
10140
+ const taskExecIds = this.taskExecutions.get(snapshot.taskId);
10141
+ if (taskExecIds) {
10142
+ taskExecIds.delete(snapshot.executionId);
10143
+ if (taskExecIds.size === 0) {
10144
+ this.taskExecutions.delete(snapshot.taskId);
10145
+ }
10146
+ }
9982
10147
  }
9983
- try {
9984
- process.kill(-child.pid, "SIGTERM");
9985
- } catch {
9986
- child.kill("SIGTERM");
9987
- return;
10148
+ }
10149
+ cancel(executionId) {
10150
+ const exec = this.running.get(executionId);
10151
+ if (!exec) return false;
10152
+ exec.cancel();
10153
+ return true;
10154
+ }
10155
+ listRunning() {
10156
+ return Array.from(this.running.values()).map((e) => ({
10157
+ executionId: e.executionId,
10158
+ taskId: e.taskId,
10159
+ startedAt: e.startedAt
10160
+ }));
10161
+ }
10162
+ dispose() {
10163
+ for (const exec of this.running.values()) {
10164
+ exec.cancel();
9988
10165
  }
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
- });
10166
+ this.running.clear();
10167
+ this.taskExecutions.clear();
10168
+ }
10169
+ };
10170
+
10171
+ // src/bridge/TaskBridgeHandler.ts
10172
+ var TaskBridgeHandler = class {
10173
+ constructor(logger, emitEvent) {
10174
+ this.logger = logger;
10175
+ this.emitEvent = emitEvent;
10176
+ this.engine = new TaskExecutionEngine(
10177
+ (taskType) => getExecutor(taskType)
10178
+ );
10179
+ this.engine.setListener({
10180
+ onStarted: (params) => this.emitEvent("task.started", params),
10181
+ onOutput: (params) => this.emitEvent("task.output", params),
10182
+ onCompleted: (params) => this.emitEvent("task.completed", params),
10183
+ onFailed: (params) => this.emitEvent("task.failed", params),
10184
+ onCancelled: (params) => this.emitEvent("task.cancelled", params)
10006
10185
  });
10007
10186
  }
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
10187
+ async execute(snapshot) {
10188
+ let earlyError;
10189
+ const executePromise = this.engine.execute(snapshot).catch((err) => {
10190
+ earlyError = err;
10028
10191
  });
10029
- if (!response.ok) {
10030
- throw createServicemeError(
10031
- "opencode_request_failed",
10032
- `OpenCode request failed: ${response.status} ${response.statusText}`
10033
- );
10034
- }
10035
- if (response.status === 204) {
10036
- return void 0;
10192
+ await Promise.resolve();
10193
+ if (earlyError) {
10194
+ throw earlyError;
10037
10195
  }
10038
- return await response.json();
10196
+ executePromise.then(void 0, (err) => {
10197
+ this.logger.error("Task execution background error", err);
10198
+ });
10199
+ return { executionId: snapshot.executionId, accepted: true };
10039
10200
  }
10040
- createQuery(workspaceRoot) {
10041
- return {
10042
- directory: workspaceRoot
10043
- };
10201
+ cancel(params) {
10202
+ const cancelled = this.engine.cancel(params.executionId);
10203
+ return { executionId: params.executionId, cancelled };
10044
10204
  }
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");
10205
+ listRunning() {
10206
+ return { executions: this.engine.listRunning() };
10064
10207
  }
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
- }
10208
+ dispose() {
10209
+ this.engine.dispose();
10081
10210
  }
10082
10211
  };
10083
10212
 
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
10213
  // src/bridge/BridgeServer.ts
10097
10214
  var CAPABILITIES = {
10098
10215
  bridge: true,
10099
- opencode: 1,
10100
10216
  json: 1,
10101
- env: 1
10217
+ env: 1,
10218
+ tasks: 1
10102
10219
  };
10103
10220
  var BridgeServer = class {
10104
- constructor(logger, options2 = {}) {
10221
+ constructor(logger) {
10105
10222
  this.logger = logger;
10106
10223
  this.handshakeCompleted = false;
10107
- this.shuttingDown = false;
10108
- this.openCodeManager = options2.openCodeManager ?? new OpenCodeManager({ logger });
10224
+ this.taskHandler = new TaskBridgeHandler(logger, (event, params) => {
10225
+ this.writeEvent(event, params);
10226
+ });
10109
10227
  }
10110
10228
  async run() {
10111
10229
  const reader = readline__namespace.createInterface({
@@ -10117,7 +10235,7 @@ var BridgeServer = class {
10117
10235
  void this.handleLine(line);
10118
10236
  });
10119
10237
  reader.on("close", () => {
10120
- void this.dispose().finally(resolve);
10238
+ resolve();
10121
10239
  });
10122
10240
  });
10123
10241
  }
@@ -10173,111 +10291,42 @@ var BridgeServer = class {
10173
10291
  }
10174
10292
  case "system.shutdown": {
10175
10293
  const shutdownRequest = request;
10294
+ this.taskHandler.dispose();
10176
10295
  this.writeSuccess(shutdownRequest.id, {
10177
10296
  shuttingDown: true
10178
10297
  });
10179
10298
  queueMicrotask(() => {
10180
- void this.dispose().finally(() => {
10181
- process.exit(0);
10182
- });
10299
+ process.exit(0);
10183
10300
  });
10184
10301
  return;
10185
10302
  }
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
- });
10303
+ case "task.execute": {
10304
+ const execRequest = request;
10305
+ try {
10306
+ const result = await this.taskHandler.execute(execRequest.params);
10307
+ this.writeSuccess(request.id, result);
10308
+ } catch (err) {
10309
+ const message = err instanceof Error ? err.message : String(err);
10310
+ if (message.includes("TASK_ALREADY_RUNNING")) {
10311
+ this.writeError(
10312
+ request.id,
10313
+ createServicemeError("task_already_running", message)
10314
+ );
10315
+ } else {
10316
+ this.writeError(request.id, err);
10226
10317
  }
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
- });
10242
- return;
10243
- }
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
- });
10318
+ }
10254
10319
  return;
10255
10320
  }
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
- });
10321
+ case "task.cancel": {
10322
+ const cancelRequest = request;
10323
+ const result = this.taskHandler.cancel(cancelRequest.params);
10324
+ this.writeSuccess(request.id, result);
10273
10325
  return;
10274
10326
  }
10275
- case "opencode.session.dispose": {
10276
- const disposeRequest = request;
10277
- this.writeSuccess(disposeRequest.id, {
10278
- disposed: true,
10279
- sessionId: disposeRequest.params.sessionId
10280
- });
10327
+ case "task.list-running": {
10328
+ const result = this.taskHandler.listRunning();
10329
+ this.writeSuccess(request.id, result);
10281
10330
  return;
10282
10331
  }
10283
10332
  default:
@@ -10319,13 +10368,6 @@ var BridgeServer = class {
10319
10368
  params
10320
10369
  });
10321
10370
  }
10322
- async dispose() {
10323
- if (this.shuttingDown) {
10324
- return;
10325
- }
10326
- this.shuttingDown = true;
10327
- await this.openCodeManager.dispose();
10328
- }
10329
10371
  };
10330
10372
 
10331
10373
  exports.BridgeServer = BridgeServer;