opencode-gitlab-duo-agentic 0.2.12 → 0.2.15

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 (2) hide show
  1. package/dist/index.js +99 -30
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -547,24 +547,26 @@ var WORKFLOW_STATUS = {
547
547
  function isCheckpointAction(action) {
548
548
  return "newCheckpoint" in action && action.newCheckpoint != null;
549
549
  }
550
- var TURN_COMPLETE_STATUSES = /* @__PURE__ */ new Set([
551
- WORKFLOW_STATUS.INPUT_REQUIRED,
550
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
552
551
  WORKFLOW_STATUS.FINISHED,
553
552
  WORKFLOW_STATUS.FAILED,
554
- WORKFLOW_STATUS.STOPPED,
555
- WORKFLOW_STATUS.PLAN_APPROVAL_REQUIRED,
556
- WORKFLOW_STATUS.TOOL_CALL_APPROVAL_REQUIRED
553
+ WORKFLOW_STATUS.STOPPED
557
554
  ]);
558
- function isTurnComplete(status) {
559
- return TURN_COMPLETE_STATUSES.has(status);
555
+ function isTerminal(status) {
556
+ return TERMINAL_STATUSES.has(status);
560
557
  }
561
- var AWAITING_RESPONSE_STATUSES = /* @__PURE__ */ new Set([
562
- WORKFLOW_STATUS.TOOL_CALL_APPROVAL_REQUIRED,
558
+ var TURN_BOUNDARY_STATUSES = /* @__PURE__ */ new Set([
563
559
  WORKFLOW_STATUS.INPUT_REQUIRED,
564
560
  WORKFLOW_STATUS.PLAN_APPROVAL_REQUIRED
565
561
  ]);
566
- function isAwaitingResponse(status) {
567
- return AWAITING_RESPONSE_STATUSES.has(status);
562
+ function isTurnBoundary(status) {
563
+ return TURN_BOUNDARY_STATUSES.has(status);
564
+ }
565
+ function isToolApproval(status) {
566
+ return status === WORKFLOW_STATUS.TOOL_CALL_APPROVAL_REQUIRED;
567
+ }
568
+ function isTurnComplete(status) {
569
+ return isTerminal(status) || isTurnBoundary(status);
568
570
  }
569
571
 
570
572
  // src/workflow/websocket-client.ts
@@ -786,13 +788,11 @@ function mapActionToToolRequest(action) {
786
788
 
787
789
  // src/utils/debug-log.ts
788
790
  import { appendFileSync } from "fs";
789
- var LOG = "/tmp/duo-workflow-debug.log";
791
+ var LOG_FILE = "/tmp/duo-workflow-debug.log";
790
792
  function dlog(msg) {
791
- try {
792
- appendFileSync(LOG, `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
793
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
794
+ appendFileSync(LOG_FILE, `[${ts}] ${msg}
793
795
  `);
794
- } catch {
795
- }
796
796
  }
797
797
 
798
798
  // src/workflow/session.ts
@@ -809,6 +809,7 @@ var WorkflowSession = class {
809
809
  #socket;
810
810
  #queue;
811
811
  #startRequestSent = false;
812
+ #pendingApproval = false;
812
813
  constructor(client, modelId, cwd) {
813
814
  this.#client = client;
814
815
  this.#tokenService = new WorkflowTokenService(client);
@@ -832,6 +833,7 @@ var WorkflowSession = class {
832
833
  this.#checkpoint = createCheckpointState();
833
834
  this.#tokenService.clear();
834
835
  this.#closeConnection();
836
+ this.#pendingApproval = false;
835
837
  this.#startRequestSent = false;
836
838
  }
837
839
  // ---------------------------------------------------------------------------
@@ -842,13 +844,30 @@ var WorkflowSession = class {
842
844
  if (!this.#workflowId) {
843
845
  this.#workflowId = await this.#createWorkflow(goal);
844
846
  }
847
+ const queue = new AsyncQueue();
848
+ this.#queue = queue;
849
+ await this.#connectSocket(queue);
850
+ }
851
+ /**
852
+ * Open a WebSocket and wire its callbacks to the given queue.
853
+ * Replaces any existing socket but does NOT create a new queue.
854
+ */
855
+ async #connectSocket(queue) {
845
856
  await this.#tokenService.get(this.#rootNamespaceId);
846
- this.#queue = new AsyncQueue();
847
- const queue = this.#queue;
848
857
  const socket = new WorkflowWebSocketClient({
849
858
  action: (action) => this.#handleAction(action, queue),
850
859
  error: (error) => queue.push({ type: "error", message: error.message }),
851
- close: (_code, _reason) => queue.close()
860
+ close: (code, reason) => {
861
+ dlog(`ws-close: code=${code} reason=${reason} pendingApproval=${this.#pendingApproval}`);
862
+ this.#socket = void 0;
863
+ if (this.#pendingApproval) {
864
+ this.#pendingApproval = false;
865
+ this.#reconnectWithApproval(queue);
866
+ } else {
867
+ this.#queue = void 0;
868
+ queue.close();
869
+ }
870
+ }
852
871
  });
853
872
  const url = buildWebSocketUrl(this.#client.instanceUrl, this.#modelId);
854
873
  await socket.connect(url, {
@@ -891,7 +910,7 @@ var WorkflowSession = class {
891
910
  * Send a tool result back to DWS on the existing connection.
892
911
  */
893
912
  sendToolResult(requestId, output, error) {
894
- dlog(`sendToolResult: reqId=${requestId} output=${output.length}b socket=${!!this.#socket}`);
913
+ dlog(`sendToolResult: reqId=${requestId} output=${output.length}b error=${error ?? "none"} socket=${!!this.#socket}`);
895
914
  if (!this.#socket) throw new Error("Not connected");
896
915
  this.#socket.send({
897
916
  actionResponse: {
@@ -924,12 +943,23 @@ var WorkflowSession = class {
924
943
  #handleAction(action, queue) {
925
944
  if (isCheckpointAction(action)) {
926
945
  const ckpt = action.newCheckpoint.checkpoint;
946
+ const status = action.newCheckpoint.status;
947
+ dlog(`checkpoint: status=${status} ckptLen=${ckpt.length}`);
927
948
  const deltas = extractAgentTextDeltas(ckpt, this.#checkpoint);
928
949
  for (const delta of deltas) {
929
950
  queue.push({ type: "text-delta", value: delta });
930
951
  }
952
+ if (deltas.length > 0) {
953
+ dlog(`checkpoint: ${deltas.length} text deltas`);
954
+ }
955
+ if (isToolApproval(status)) {
956
+ dlog(`checkpoint: TOOL_APPROVAL \u2192 pendingApproval=true (waiting for DWS close)`);
957
+ this.#pendingApproval = true;
958
+ return;
959
+ }
931
960
  const toolRequests = extractToolRequests(ckpt, this.#checkpoint);
932
961
  for (const req of toolRequests) {
962
+ dlog(`checkpoint: tool-request name=${req.toolName} reqId=${req.requestId}`);
933
963
  queue.push({
934
964
  type: "tool-request",
935
965
  requestId: req.requestId,
@@ -937,32 +967,74 @@ var WorkflowSession = class {
937
967
  args: req.args
938
968
  });
939
969
  }
940
- const status = action.newCheckpoint.status;
941
- if (isTurnComplete(status) && !isAwaitingResponse(status)) {
942
- dlog(`action: checkpoint status=${status} CLOSE`);
970
+ if (isTurnComplete(status)) {
971
+ dlog(`checkpoint: turnComplete \u2192 close queue+connection`);
943
972
  queue.close();
944
973
  this.#closeConnection();
945
- } else {
946
- dlog(`action: checkpoint status=${status} KEEP-ALIVE`);
947
974
  }
948
975
  return;
949
976
  }
950
977
  const toolAction = action;
951
978
  const mapped = mapActionToToolRequest(toolAction);
952
979
  if (mapped) {
953
- dlog(`action: standalone ${mapped.toolName} reqId=${mapped.requestId}`);
980
+ dlog(`standalone: ${mapped.toolName} reqId=${mapped.requestId} args=${JSON.stringify(mapped.args).slice(0, 200)}`);
954
981
  queue.push({
955
982
  type: "tool-request",
956
983
  requestId: mapped.requestId,
957
984
  toolName: mapped.toolName,
958
985
  args: mapped.args
959
986
  });
987
+ } else {
988
+ dlog(`standalone: UNMAPPED action keys=${Object.keys(action).join(",")}`);
960
989
  }
961
990
  }
962
991
  // ---------------------------------------------------------------------------
963
992
  // Private: connection management
964
993
  // ---------------------------------------------------------------------------
994
+ /**
995
+ * Auto-approve at DWS protocol level and reconnect.
996
+ *
997
+ * DWS closed the stream after TOOL_CALL_APPROVAL_REQUIRED. We open a new
998
+ * WebSocket, send startRequest with approval, and wire it to the SAME queue
999
+ * so Phase 3 in the model continues consuming events seamlessly.
1000
+ *
1001
+ * The actual tool execution still goes through OpenCode's permission system
1002
+ * when the standalone action arrives on the new stream.
1003
+ */
1004
+ #reconnectWithApproval(queue) {
1005
+ dlog(`reconnectWithApproval: starting (workflowId=${this.#workflowId})`);
1006
+ this.#connectSocket(queue).then(() => {
1007
+ if (!this.#socket || !this.#workflowId) {
1008
+ dlog(`reconnectWithApproval: FAILED no socket/workflowId`);
1009
+ queue.close();
1010
+ return;
1011
+ }
1012
+ const mcpTools = this.#toolsConfig?.mcpTools ?? [];
1013
+ dlog(`reconnectWithApproval: sending startRequest with approval (mcpTools=${mcpTools.length})`);
1014
+ this.#socket.send({
1015
+ startRequest: {
1016
+ workflowID: this.#workflowId,
1017
+ clientVersion: WORKFLOW_CLIENT_VERSION,
1018
+ workflowDefinition: WORKFLOW_DEFINITION,
1019
+ goal: "",
1020
+ workflowMetadata: JSON.stringify({ extended_logging: false }),
1021
+ clientCapabilities: ["shell_command"],
1022
+ mcpTools,
1023
+ additional_context: [],
1024
+ preapproved_tools: mcpTools.map((t) => t.name),
1025
+ approval: { approval: {} }
1026
+ }
1027
+ });
1028
+ this.#startRequestSent = true;
1029
+ dlog(`reconnectWithApproval: approval sent, waiting for standalone actions`);
1030
+ }).catch((err) => {
1031
+ dlog(`reconnectWithApproval: ERROR ${err instanceof Error ? err.message : String(err)}`);
1032
+ this.#queue = void 0;
1033
+ queue.close();
1034
+ });
1035
+ }
965
1036
  #closeConnection() {
1037
+ this.#pendingApproval = false;
966
1038
  this.#socket?.close();
967
1039
  this.#socket = void 0;
968
1040
  this.#queue = void 0;
@@ -1451,10 +1523,7 @@ var DuoWorkflowModel = class {
1451
1523
  const freshResults = toolResults.filter(
1452
1524
  (r) => !model.#sentToolCallIds.has(r.toolCallId)
1453
1525
  );
1454
- dlog(`phase1: ${toolResults.length} total, ${freshResults.length} fresh, sentIds=${[...model.#sentToolCallIds].join(",")}`);
1455
- for (const r of toolResults) {
1456
- dlog(` tr: id=${r.toolCallId} inSent=${model.#sentToolCallIds.has(r.toolCallId)} inPending=${model.#pendingToolRequests.has(r.toolCallId)} out=${r.output.length}b`);
1457
- }
1526
+ dlog(`phase1: ${toolResults.length} total, ${freshResults.length} fresh`);
1458
1527
  let sentToolResults = false;
1459
1528
  for (const result of freshResults) {
1460
1529
  const hashIdx = result.toolCallId.indexOf("#");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-gitlab-duo-agentic",
3
- "version": "0.2.12",
3
+ "version": "0.2.15",
4
4
  "description": "OpenCode plugin and provider for GitLab Duo Agentic workflows",
5
5
  "license": "MIT",
6
6
  "type": "module",