opencode-gitlab-duo-agentic 0.2.12 → 0.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +78 -42
- 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
|
|
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
|
|
559
|
-
return
|
|
555
|
+
function isTerminal(status) {
|
|
556
|
+
return TERMINAL_STATUSES.has(status);
|
|
560
557
|
}
|
|
561
|
-
var
|
|
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
|
|
567
|
-
return
|
|
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
|
|
@@ -784,17 +786,6 @@ function mapActionToToolRequest(action) {
|
|
|
784
786
|
return null;
|
|
785
787
|
}
|
|
786
788
|
|
|
787
|
-
// src/utils/debug-log.ts
|
|
788
|
-
import { appendFileSync } from "fs";
|
|
789
|
-
var LOG = "/tmp/duo-workflow-debug.log";
|
|
790
|
-
function dlog(msg) {
|
|
791
|
-
try {
|
|
792
|
-
appendFileSync(LOG, `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
793
|
-
`);
|
|
794
|
-
} catch {
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
|
|
798
789
|
// src/workflow/session.ts
|
|
799
790
|
var WorkflowSession = class {
|
|
800
791
|
#client;
|
|
@@ -809,6 +800,7 @@ var WorkflowSession = class {
|
|
|
809
800
|
#socket;
|
|
810
801
|
#queue;
|
|
811
802
|
#startRequestSent = false;
|
|
803
|
+
#pendingApproval = false;
|
|
812
804
|
constructor(client, modelId, cwd) {
|
|
813
805
|
this.#client = client;
|
|
814
806
|
this.#tokenService = new WorkflowTokenService(client);
|
|
@@ -832,6 +824,7 @@ var WorkflowSession = class {
|
|
|
832
824
|
this.#checkpoint = createCheckpointState();
|
|
833
825
|
this.#tokenService.clear();
|
|
834
826
|
this.#closeConnection();
|
|
827
|
+
this.#pendingApproval = false;
|
|
835
828
|
this.#startRequestSent = false;
|
|
836
829
|
}
|
|
837
830
|
// ---------------------------------------------------------------------------
|
|
@@ -842,13 +835,29 @@ var WorkflowSession = class {
|
|
|
842
835
|
if (!this.#workflowId) {
|
|
843
836
|
this.#workflowId = await this.#createWorkflow(goal);
|
|
844
837
|
}
|
|
838
|
+
const queue = new AsyncQueue();
|
|
839
|
+
this.#queue = queue;
|
|
840
|
+
await this.#connectSocket(queue);
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Open a WebSocket and wire its callbacks to the given queue.
|
|
844
|
+
* Replaces any existing socket but does NOT create a new queue.
|
|
845
|
+
*/
|
|
846
|
+
async #connectSocket(queue) {
|
|
845
847
|
await this.#tokenService.get(this.#rootNamespaceId);
|
|
846
|
-
this.#queue = new AsyncQueue();
|
|
847
|
-
const queue = this.#queue;
|
|
848
848
|
const socket = new WorkflowWebSocketClient({
|
|
849
849
|
action: (action) => this.#handleAction(action, queue),
|
|
850
850
|
error: (error) => queue.push({ type: "error", message: error.message }),
|
|
851
|
-
close: (_code, _reason) =>
|
|
851
|
+
close: (_code, _reason) => {
|
|
852
|
+
this.#socket = void 0;
|
|
853
|
+
if (this.#pendingApproval) {
|
|
854
|
+
this.#pendingApproval = false;
|
|
855
|
+
this.#reconnectWithApproval(queue);
|
|
856
|
+
} else {
|
|
857
|
+
this.#queue = void 0;
|
|
858
|
+
queue.close();
|
|
859
|
+
}
|
|
860
|
+
}
|
|
852
861
|
});
|
|
853
862
|
const url = buildWebSocketUrl(this.#client.instanceUrl, this.#modelId);
|
|
854
863
|
await socket.connect(url, {
|
|
@@ -891,7 +900,6 @@ var WorkflowSession = class {
|
|
|
891
900
|
* Send a tool result back to DWS on the existing connection.
|
|
892
901
|
*/
|
|
893
902
|
sendToolResult(requestId, output, error) {
|
|
894
|
-
dlog(`sendToolResult: reqId=${requestId} output=${output.length}b socket=${!!this.#socket}`);
|
|
895
903
|
if (!this.#socket) throw new Error("Not connected");
|
|
896
904
|
this.#socket.send({
|
|
897
905
|
actionResponse: {
|
|
@@ -924,10 +932,15 @@ var WorkflowSession = class {
|
|
|
924
932
|
#handleAction(action, queue) {
|
|
925
933
|
if (isCheckpointAction(action)) {
|
|
926
934
|
const ckpt = action.newCheckpoint.checkpoint;
|
|
935
|
+
const status = action.newCheckpoint.status;
|
|
927
936
|
const deltas = extractAgentTextDeltas(ckpt, this.#checkpoint);
|
|
928
937
|
for (const delta of deltas) {
|
|
929
938
|
queue.push({ type: "text-delta", value: delta });
|
|
930
939
|
}
|
|
940
|
+
if (isToolApproval(status)) {
|
|
941
|
+
this.#pendingApproval = true;
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
931
944
|
const toolRequests = extractToolRequests(ckpt, this.#checkpoint);
|
|
932
945
|
for (const req of toolRequests) {
|
|
933
946
|
queue.push({
|
|
@@ -937,20 +950,15 @@ var WorkflowSession = class {
|
|
|
937
950
|
args: req.args
|
|
938
951
|
});
|
|
939
952
|
}
|
|
940
|
-
|
|
941
|
-
if (isTurnComplete(status) && !isAwaitingResponse(status)) {
|
|
942
|
-
dlog(`action: checkpoint status=${status} CLOSE`);
|
|
953
|
+
if (isTurnComplete(status)) {
|
|
943
954
|
queue.close();
|
|
944
955
|
this.#closeConnection();
|
|
945
|
-
} else {
|
|
946
|
-
dlog(`action: checkpoint status=${status} KEEP-ALIVE`);
|
|
947
956
|
}
|
|
948
957
|
return;
|
|
949
958
|
}
|
|
950
959
|
const toolAction = action;
|
|
951
960
|
const mapped = mapActionToToolRequest(toolAction);
|
|
952
961
|
if (mapped) {
|
|
953
|
-
dlog(`action: standalone ${mapped.toolName} reqId=${mapped.requestId}`);
|
|
954
962
|
queue.push({
|
|
955
963
|
type: "tool-request",
|
|
956
964
|
requestId: mapped.requestId,
|
|
@@ -962,7 +970,45 @@ var WorkflowSession = class {
|
|
|
962
970
|
// ---------------------------------------------------------------------------
|
|
963
971
|
// Private: connection management
|
|
964
972
|
// ---------------------------------------------------------------------------
|
|
973
|
+
/**
|
|
974
|
+
* Auto-approve at DWS protocol level and reconnect.
|
|
975
|
+
*
|
|
976
|
+
* DWS closed the stream after TOOL_CALL_APPROVAL_REQUIRED. We open a new
|
|
977
|
+
* WebSocket, send startRequest with approval, and wire it to the SAME queue
|
|
978
|
+
* so Phase 3 in the model continues consuming events seamlessly.
|
|
979
|
+
*
|
|
980
|
+
* The actual tool execution still goes through OpenCode's permission system
|
|
981
|
+
* when the standalone action arrives on the new stream.
|
|
982
|
+
*/
|
|
983
|
+
#reconnectWithApproval(queue) {
|
|
984
|
+
this.#connectSocket(queue).then(() => {
|
|
985
|
+
if (!this.#socket || !this.#workflowId) {
|
|
986
|
+
queue.close();
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
const mcpTools = this.#toolsConfig?.mcpTools ?? [];
|
|
990
|
+
this.#socket.send({
|
|
991
|
+
startRequest: {
|
|
992
|
+
workflowID: this.#workflowId,
|
|
993
|
+
clientVersion: WORKFLOW_CLIENT_VERSION,
|
|
994
|
+
workflowDefinition: WORKFLOW_DEFINITION,
|
|
995
|
+
goal: "",
|
|
996
|
+
workflowMetadata: JSON.stringify({ extended_logging: false }),
|
|
997
|
+
clientCapabilities: ["shell_command"],
|
|
998
|
+
mcpTools,
|
|
999
|
+
additional_context: [],
|
|
1000
|
+
preapproved_tools: mcpTools.map((t) => t.name),
|
|
1001
|
+
approval: { approval: {} }
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
this.#startRequestSent = true;
|
|
1005
|
+
}).catch(() => {
|
|
1006
|
+
this.#queue = void 0;
|
|
1007
|
+
queue.close();
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
965
1010
|
#closeConnection() {
|
|
1011
|
+
this.#pendingApproval = false;
|
|
966
1012
|
this.#socket?.close();
|
|
967
1013
|
this.#socket = void 0;
|
|
968
1014
|
this.#queue = void 0;
|
|
@@ -1420,7 +1466,6 @@ var DuoWorkflowModel = class {
|
|
|
1420
1466
|
const toolResults = extractToolResults(options.prompt);
|
|
1421
1467
|
const session = this.#resolveSession(sessionID);
|
|
1422
1468
|
const textId = randomUUID3();
|
|
1423
|
-
dlog(`doStream: goal=${goal?.length ?? 0}ch toolResults=${toolResults.length} hasStarted=${session.hasStarted} pending=${this.#pendingToolRequests.size} sent=${this.#sentToolCallIds.size}`);
|
|
1424
1469
|
if (sessionID !== this.#stateSessionId) {
|
|
1425
1470
|
this.#pendingToolRequests.clear();
|
|
1426
1471
|
this.#multiCallGroups.clear();
|
|
@@ -1451,10 +1496,6 @@ var DuoWorkflowModel = class {
|
|
|
1451
1496
|
const freshResults = toolResults.filter(
|
|
1452
1497
|
(r) => !model.#sentToolCallIds.has(r.toolCallId)
|
|
1453
1498
|
);
|
|
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
|
-
}
|
|
1458
1499
|
let sentToolResults = false;
|
|
1459
1500
|
for (const result of freshResults) {
|
|
1460
1501
|
const hashIdx = result.toolCallId.indexOf("#");
|
|
@@ -1479,11 +1520,9 @@ var DuoWorkflowModel = class {
|
|
|
1479
1520
|
}
|
|
1480
1521
|
const pending = model.#pendingToolRequests.get(result.toolCallId);
|
|
1481
1522
|
if (!pending) {
|
|
1482
|
-
dlog(`phase1: SKIP ${result.toolCallId} (not pending)`);
|
|
1483
1523
|
model.#sentToolCallIds.add(result.toolCallId);
|
|
1484
1524
|
continue;
|
|
1485
1525
|
}
|
|
1486
|
-
dlog(`phase1: SEND ${result.toolCallId} output=${result.output.length}b`);
|
|
1487
1526
|
session.sendToolResult(result.toolCallId, result.output, result.error);
|
|
1488
1527
|
sentToolResults = true;
|
|
1489
1528
|
model.#sentToolCallIds.add(result.toolCallId);
|
|
@@ -1549,11 +1588,8 @@ var DuoWorkflowModel = class {
|
|
|
1549
1588
|
try {
|
|
1550
1589
|
mapped = mapDuoToolRequest(event.toolName, event.args);
|
|
1551
1590
|
} catch {
|
|
1552
|
-
dlog(`phase3: MAPPING FAILED ${event.toolName}`);
|
|
1553
1591
|
continue;
|
|
1554
1592
|
}
|
|
1555
|
-
const mName = Array.isArray(mapped) ? mapped.map((m) => m.toolName).join(",") : mapped.toolName;
|
|
1556
|
-
dlog(`phase3: tool-request ${event.toolName} \u2192 ${mName} reqId=${event.requestId}`);
|
|
1557
1593
|
if (hasText) {
|
|
1558
1594
|
controller.enqueue({ type: "text-end", id: textId });
|
|
1559
1595
|
}
|