@openma/deepseek-harness-acp 0.4.1 → 0.4.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/bridge.js +135 -16
  2. package/dist/index.js +135 -16
  3. package/package.json +1 -1
package/dist/bridge.js CHANGED
@@ -265,8 +265,14 @@ var SessionProjection = class {
265
265
  toolCalls = /* @__PURE__ */ new Map();
266
266
  window = emptyWindow();
267
267
  lastContextUse;
268
- constructor(contextWindow) {
268
+ /** Client renders `_meta.terminal_output` display terminals (Zed extension). */
269
+ terminalOutput;
270
+ /** Session working directory, advertised on display terminals. */
271
+ cwd;
272
+ constructor(contextWindow, options) {
269
273
  this.contextWindow = contextWindow;
274
+ this.terminalOutput = options?.terminalOutput ?? false;
275
+ this.cwd = options?.cwd;
270
276
  }
271
277
  /** Reset per-prompt accumulators; call when a new `session/prompt` starts. */
272
278
  beginPrompt() {
@@ -395,6 +401,7 @@ var SessionProjection = class {
395
401
  const rawArguments = typeof data["arguments"] === "string" ? data["arguments"] : "{}";
396
402
  const facts = classifyToolCall(name3, rawArguments);
397
403
  this.toolCalls.set(callId, facts);
404
+ const displayTerminal = this.terminalOutput && facts.kind === "execute";
398
405
  return [
399
406
  {
400
407
  sessionUpdate: "tool_call",
@@ -404,7 +411,16 @@ var SessionProjection = class {
404
411
  kind: facts.kind,
405
412
  status: "in_progress",
406
413
  rawInput: facts.rawInput,
407
- ...facts.locations.length > 0 ? { locations: facts.locations } : {}
414
+ ...facts.locations.length > 0 ? { locations: facts.locations } : {},
415
+ ...displayTerminal ? {
416
+ content: [{ type: "terminal", terminalId: callId }],
417
+ _meta: {
418
+ terminal_info: {
419
+ terminal_id: callId,
420
+ ...this.cwd !== void 0 ? { cwd: this.cwd } : {}
421
+ }
422
+ }
423
+ } : {}
408
424
  }
409
425
  ];
410
426
  }
@@ -421,6 +437,7 @@ var SessionProjection = class {
421
437
  })() : void 0
422
438
  ) ?? asString(data["callId"]);
423
439
  if (callId === void 0) return [];
440
+ const facts = this.toolCalls.get(callId);
424
441
  const { failed, text, diffs } = extractToolResult(data);
425
442
  const content = diffs.map((diff) => ({
426
443
  type: "diff",
@@ -428,8 +445,36 @@ var SessionProjection = class {
428
445
  ...diff.oldText !== null ? { oldText: diff.oldText } : {},
429
446
  newText: diff.newText
430
447
  }));
448
+ const isCommand = facts?.kind === "execute";
449
+ if (this.terminalOutput && isCommand) {
450
+ this.toolCalls.delete(callId);
451
+ return [
452
+ ...text.length > 0 ? [
453
+ {
454
+ sessionUpdate: "tool_call_update",
455
+ toolCallId: callId,
456
+ _meta: { terminal_output: { terminal_id: callId, data: text } }
457
+ }
458
+ ] : [],
459
+ {
460
+ sessionUpdate: "tool_call_update",
461
+ toolCallId: callId,
462
+ status: failed ? "failed" : "completed",
463
+ ...text.length > 0 ? { rawOutput: { output: text, isError: failed } } : {},
464
+ _meta: {
465
+ terminal_exit: { terminal_id: callId, exit_code: failed ? 1 : 0, signal: null }
466
+ }
467
+ }
468
+ ];
469
+ }
431
470
  if (text.length > 0 && diffs.length === 0) {
432
- const block = { type: "text", text };
471
+ const block = {
472
+ type: "text",
473
+ text: isCommand ? `\`\`\`sh
474
+ ${text.replace(/\n+$/, "")}
475
+ \`\`\`
476
+ ` : text
477
+ };
433
478
  content.push({ type: "content", content: block });
434
479
  }
435
480
  this.toolCalls.delete(callId);
@@ -591,6 +636,7 @@ async function apply(ctx, config = {}) {
591
636
  const sessions = /* @__PURE__ */ new Map();
592
637
  let closed = false;
593
638
  let conn;
639
+ let clientTerminalOutput = false;
594
640
  const modelCandidates = () => {
595
641
  const seen = /* @__PURE__ */ new Set();
596
642
  if (config.model !== void 0) seen.add(config.model);
@@ -908,6 +954,7 @@ async function apply(ctx, config = {}) {
908
954
  delete record.selection;
909
955
  ensureSelection(record);
910
956
  setApprovalPolicy(record, record.approvals);
957
+ publishCommands(acpSessionId, record);
911
958
  };
912
959
  const setApprovalPolicy = (record, policy) => {
913
960
  try {
@@ -1039,18 +1086,64 @@ async function apply(ctx, config = {}) {
1039
1086
  }
1040
1087
  return result;
1041
1088
  };
1042
- const publishCommands = (sessionId) => {
1043
- notify(sessionId, {
1044
- sessionUpdate: "available_commands_update",
1045
- availableCommands: [
1046
- { name: "status", description: "Show adapter, model, mode, and token status" },
1047
- {
1048
- name: "login",
1049
- description: "Save a DeepSeek API key into the harness credential store (~/.dsh/.credentials.yaml)",
1050
- input: { hint: "<api-key>" }
1051
- },
1052
- { name: "logout", description: "Remove the API key stored in the harness credential store" }
1053
- ]
1089
+ const BUILTIN_COMMANDS = [
1090
+ { name: "status", description: "Show adapter, model, mode, and token status" },
1091
+ {
1092
+ name: "login",
1093
+ description: "Save a DeepSeek API key into the harness credential store (~/.dsh/.credentials.yaml)",
1094
+ input: { hint: "<api-key>" }
1095
+ },
1096
+ { name: "logout", description: "Remove the API key stored in the harness credential store" }
1097
+ ];
1098
+ const availableCommandsFor = async (record) => {
1099
+ const list = [...BUILTIN_COMMANDS];
1100
+ const seen = new Set(list.map((command) => command.name));
1101
+ const commandsSvc = ctx.get("commands");
1102
+ if (commandsSvc !== void 0) {
1103
+ try {
1104
+ for (const descriptor of commandsSvc.list(record.agent)) {
1105
+ if (seen.has(descriptor.name)) continue;
1106
+ seen.add(descriptor.name);
1107
+ list.push({
1108
+ name: descriptor.name,
1109
+ description: descriptor.description,
1110
+ ...descriptor.input !== void 0 ? { input: { hint: descriptor.input.hint } } : {}
1111
+ });
1112
+ }
1113
+ } catch (error) {
1114
+ logWarn(`command listing failed: ${String(error)}`);
1115
+ }
1116
+ }
1117
+ const skillsSvc = ctx.get("skills");
1118
+ if (skillsSvc !== void 0) {
1119
+ try {
1120
+ const skills = await skillsSvc.list({
1121
+ ...record.agent.session.header.cwd !== void 0 ? { cwd: record.agent.session.header.cwd } : {},
1122
+ scope: record.agent
1123
+ });
1124
+ for (const skill of skills) {
1125
+ if (skill.invocation.userInvocable !== true) continue;
1126
+ if (seen.has(skill.name)) continue;
1127
+ seen.add(skill.name);
1128
+ list.push({
1129
+ name: skill.name,
1130
+ description: skill.description,
1131
+ input: { hint: "instructions for the skill" }
1132
+ });
1133
+ }
1134
+ } catch (error) {
1135
+ logDebug(`skill listing failed: ${String(error)}`);
1136
+ }
1137
+ }
1138
+ return list;
1139
+ };
1140
+ const publishCommands = (sessionId, record) => {
1141
+ const target = record ?? sessions.get(sessionId);
1142
+ if (target === void 0) return;
1143
+ void availableCommandsFor(target).then((availableCommands) => {
1144
+ notify(sessionId, { sessionUpdate: "available_commands_update", availableCommands });
1145
+ }).catch((error) => {
1146
+ logWarn(`publishing commands failed: ${String(error)}`);
1054
1147
  });
1055
1148
  };
1056
1149
  const credentialStore = () => {
@@ -1143,7 +1236,10 @@ async function apply(ctx, config = {}) {
1143
1236
  const record = {
1144
1237
  agent,
1145
1238
  dispose,
1146
- projection: projection ?? new SessionProjection(),
1239
+ projection: projection ?? new SessionProjection(void 0, {
1240
+ terminalOutput: clientTerminalOutput,
1241
+ ...agent.session.header.cwd !== void 0 ? { cwd: agent.session.header.cwd } : {}
1242
+ }),
1147
1243
  modeId,
1148
1244
  model,
1149
1245
  approvals: modeId === "danger-full-access" ? "never" : "ask",
@@ -1213,6 +1309,8 @@ async function apply(ctx, config = {}) {
1213
1309
  initialize(params) {
1214
1310
  const hasPersistence = ctx.get("sessionPersistence") !== void 0;
1215
1311
  const requested = params.protocolVersion;
1312
+ const capsMeta = params.clientCapabilities?.["_meta"];
1313
+ clientTerminalOutput = capsMeta !== null && typeof capsMeta === "object" ? capsMeta["terminal_output"] === true : false;
1216
1314
  return Promise.resolve({
1217
1315
  protocolVersion: typeof requested === "number" && requested >= 1 && requested < PROTOCOL_VERSION ? requested : PROTOCOL_VERSION,
1218
1316
  agentInfo: { name: "dsh-acp", title: "DeepSeek Harness", version: VERSION },
@@ -1393,6 +1491,27 @@ async function apply(ctx, config = {}) {
1393
1491
  return respond(await loginText(trimmed.slice(commandMatch[0].length)));
1394
1492
  }
1395
1493
  if (commandMatch?.[1] === "logout") return respond(await logoutText());
1494
+ if (commandMatch !== null && commandMatch[1] !== void 0) {
1495
+ const commandsSvc = ctx.get("commands");
1496
+ if (commandsSvc !== void 0) {
1497
+ let execution;
1498
+ try {
1499
+ execution = await commandsSvc.execute(
1500
+ record.agent,
1501
+ trimmed,
1502
+ new AbortController().signal
1503
+ );
1504
+ } catch (error) {
1505
+ return respond(`\u26A0 /${commandMatch[1]} failed: ${errorChain2(error)}`);
1506
+ }
1507
+ if (execution !== void 0) {
1508
+ const { result } = execution;
1509
+ const text = result.text ?? (result.kind === "success" ? `/${commandMatch[1]} \u2713` : `/${commandMatch[1]} failed`);
1510
+ publishCommands(params.sessionId, record);
1511
+ return respond(result.kind === "error" ? `\u26A0 ${text}` : text);
1512
+ }
1513
+ }
1514
+ }
1396
1515
  if (!await credentialPresent()) {
1397
1516
  return respond(
1398
1517
  [
package/dist/index.js CHANGED
@@ -290,8 +290,14 @@ var SessionProjection = class {
290
290
  toolCalls = /* @__PURE__ */ new Map();
291
291
  window = emptyWindow();
292
292
  lastContextUse;
293
- constructor(contextWindow) {
293
+ /** Client renders `_meta.terminal_output` display terminals (Zed extension). */
294
+ terminalOutput;
295
+ /** Session working directory, advertised on display terminals. */
296
+ cwd;
297
+ constructor(contextWindow, options) {
294
298
  this.contextWindow = contextWindow;
299
+ this.terminalOutput = options?.terminalOutput ?? false;
300
+ this.cwd = options?.cwd;
295
301
  }
296
302
  /** Reset per-prompt accumulators; call when a new `session/prompt` starts. */
297
303
  beginPrompt() {
@@ -420,6 +426,7 @@ var SessionProjection = class {
420
426
  const rawArguments = typeof data["arguments"] === "string" ? data["arguments"] : "{}";
421
427
  const facts = classifyToolCall(name3, rawArguments);
422
428
  this.toolCalls.set(callId, facts);
429
+ const displayTerminal = this.terminalOutput && facts.kind === "execute";
423
430
  return [
424
431
  {
425
432
  sessionUpdate: "tool_call",
@@ -429,7 +436,16 @@ var SessionProjection = class {
429
436
  kind: facts.kind,
430
437
  status: "in_progress",
431
438
  rawInput: facts.rawInput,
432
- ...facts.locations.length > 0 ? { locations: facts.locations } : {}
439
+ ...facts.locations.length > 0 ? { locations: facts.locations } : {},
440
+ ...displayTerminal ? {
441
+ content: [{ type: "terminal", terminalId: callId }],
442
+ _meta: {
443
+ terminal_info: {
444
+ terminal_id: callId,
445
+ ...this.cwd !== void 0 ? { cwd: this.cwd } : {}
446
+ }
447
+ }
448
+ } : {}
433
449
  }
434
450
  ];
435
451
  }
@@ -446,6 +462,7 @@ var SessionProjection = class {
446
462
  })() : void 0
447
463
  ) ?? asString(data["callId"]);
448
464
  if (callId === void 0) return [];
465
+ const facts = this.toolCalls.get(callId);
449
466
  const { failed, text, diffs } = extractToolResult(data);
450
467
  const content = diffs.map((diff) => ({
451
468
  type: "diff",
@@ -453,8 +470,36 @@ var SessionProjection = class {
453
470
  ...diff.oldText !== null ? { oldText: diff.oldText } : {},
454
471
  newText: diff.newText
455
472
  }));
473
+ const isCommand = facts?.kind === "execute";
474
+ if (this.terminalOutput && isCommand) {
475
+ this.toolCalls.delete(callId);
476
+ return [
477
+ ...text.length > 0 ? [
478
+ {
479
+ sessionUpdate: "tool_call_update",
480
+ toolCallId: callId,
481
+ _meta: { terminal_output: { terminal_id: callId, data: text } }
482
+ }
483
+ ] : [],
484
+ {
485
+ sessionUpdate: "tool_call_update",
486
+ toolCallId: callId,
487
+ status: failed ? "failed" : "completed",
488
+ ...text.length > 0 ? { rawOutput: { output: text, isError: failed } } : {},
489
+ _meta: {
490
+ terminal_exit: { terminal_id: callId, exit_code: failed ? 1 : 0, signal: null }
491
+ }
492
+ }
493
+ ];
494
+ }
456
495
  if (text.length > 0 && diffs.length === 0) {
457
- const block = { type: "text", text };
496
+ const block = {
497
+ type: "text",
498
+ text: isCommand ? `\`\`\`sh
499
+ ${text.replace(/\n+$/, "")}
500
+ \`\`\`
501
+ ` : text
502
+ };
458
503
  content.push({ type: "content", content: block });
459
504
  }
460
505
  this.toolCalls.delete(callId);
@@ -616,6 +661,7 @@ async function apply(ctx, config = {}) {
616
661
  const sessions = /* @__PURE__ */ new Map();
617
662
  let closed = false;
618
663
  let conn;
664
+ let clientTerminalOutput = false;
619
665
  const modelCandidates = () => {
620
666
  const seen = /* @__PURE__ */ new Set();
621
667
  if (config.model !== void 0) seen.add(config.model);
@@ -933,6 +979,7 @@ async function apply(ctx, config = {}) {
933
979
  delete record.selection;
934
980
  ensureSelection(record);
935
981
  setApprovalPolicy(record, record.approvals);
982
+ publishCommands(acpSessionId, record);
936
983
  };
937
984
  const setApprovalPolicy = (record, policy) => {
938
985
  try {
@@ -1064,18 +1111,64 @@ async function apply(ctx, config = {}) {
1064
1111
  }
1065
1112
  return result;
1066
1113
  };
1067
- const publishCommands = (sessionId) => {
1068
- notify(sessionId, {
1069
- sessionUpdate: "available_commands_update",
1070
- availableCommands: [
1071
- { name: "status", description: "Show adapter, model, mode, and token status" },
1072
- {
1073
- name: "login",
1074
- description: "Save a DeepSeek API key into the harness credential store (~/.dsh/.credentials.yaml)",
1075
- input: { hint: "<api-key>" }
1076
- },
1077
- { name: "logout", description: "Remove the API key stored in the harness credential store" }
1078
- ]
1114
+ const BUILTIN_COMMANDS = [
1115
+ { name: "status", description: "Show adapter, model, mode, and token status" },
1116
+ {
1117
+ name: "login",
1118
+ description: "Save a DeepSeek API key into the harness credential store (~/.dsh/.credentials.yaml)",
1119
+ input: { hint: "<api-key>" }
1120
+ },
1121
+ { name: "logout", description: "Remove the API key stored in the harness credential store" }
1122
+ ];
1123
+ const availableCommandsFor = async (record) => {
1124
+ const list = [...BUILTIN_COMMANDS];
1125
+ const seen = new Set(list.map((command) => command.name));
1126
+ const commandsSvc = ctx.get("commands");
1127
+ if (commandsSvc !== void 0) {
1128
+ try {
1129
+ for (const descriptor of commandsSvc.list(record.agent)) {
1130
+ if (seen.has(descriptor.name)) continue;
1131
+ seen.add(descriptor.name);
1132
+ list.push({
1133
+ name: descriptor.name,
1134
+ description: descriptor.description,
1135
+ ...descriptor.input !== void 0 ? { input: { hint: descriptor.input.hint } } : {}
1136
+ });
1137
+ }
1138
+ } catch (error) {
1139
+ logWarn(`command listing failed: ${String(error)}`);
1140
+ }
1141
+ }
1142
+ const skillsSvc = ctx.get("skills");
1143
+ if (skillsSvc !== void 0) {
1144
+ try {
1145
+ const skills = await skillsSvc.list({
1146
+ ...record.agent.session.header.cwd !== void 0 ? { cwd: record.agent.session.header.cwd } : {},
1147
+ scope: record.agent
1148
+ });
1149
+ for (const skill of skills) {
1150
+ if (skill.invocation.userInvocable !== true) continue;
1151
+ if (seen.has(skill.name)) continue;
1152
+ seen.add(skill.name);
1153
+ list.push({
1154
+ name: skill.name,
1155
+ description: skill.description,
1156
+ input: { hint: "instructions for the skill" }
1157
+ });
1158
+ }
1159
+ } catch (error) {
1160
+ logDebug(`skill listing failed: ${String(error)}`);
1161
+ }
1162
+ }
1163
+ return list;
1164
+ };
1165
+ const publishCommands = (sessionId, record) => {
1166
+ const target = record ?? sessions.get(sessionId);
1167
+ if (target === void 0) return;
1168
+ void availableCommandsFor(target).then((availableCommands) => {
1169
+ notify(sessionId, { sessionUpdate: "available_commands_update", availableCommands });
1170
+ }).catch((error) => {
1171
+ logWarn(`publishing commands failed: ${String(error)}`);
1079
1172
  });
1080
1173
  };
1081
1174
  const credentialStore = () => {
@@ -1168,7 +1261,10 @@ async function apply(ctx, config = {}) {
1168
1261
  const record = {
1169
1262
  agent,
1170
1263
  dispose,
1171
- projection: projection ?? new SessionProjection(),
1264
+ projection: projection ?? new SessionProjection(void 0, {
1265
+ terminalOutput: clientTerminalOutput,
1266
+ ...agent.session.header.cwd !== void 0 ? { cwd: agent.session.header.cwd } : {}
1267
+ }),
1172
1268
  modeId,
1173
1269
  model,
1174
1270
  approvals: modeId === "danger-full-access" ? "never" : "ask",
@@ -1238,6 +1334,8 @@ async function apply(ctx, config = {}) {
1238
1334
  initialize(params) {
1239
1335
  const hasPersistence = ctx.get("sessionPersistence") !== void 0;
1240
1336
  const requested = params.protocolVersion;
1337
+ const capsMeta = params.clientCapabilities?.["_meta"];
1338
+ clientTerminalOutput = capsMeta !== null && typeof capsMeta === "object" ? capsMeta["terminal_output"] === true : false;
1241
1339
  return Promise.resolve({
1242
1340
  protocolVersion: typeof requested === "number" && requested >= 1 && requested < PROTOCOL_VERSION ? requested : PROTOCOL_VERSION,
1243
1341
  agentInfo: { name: "dsh-acp", title: "DeepSeek Harness", version: VERSION },
@@ -1418,6 +1516,27 @@ async function apply(ctx, config = {}) {
1418
1516
  return respond(await loginText(trimmed.slice(commandMatch[0].length)));
1419
1517
  }
1420
1518
  if (commandMatch?.[1] === "logout") return respond(await logoutText());
1519
+ if (commandMatch !== null && commandMatch[1] !== void 0) {
1520
+ const commandsSvc = ctx.get("commands");
1521
+ if (commandsSvc !== void 0) {
1522
+ let execution;
1523
+ try {
1524
+ execution = await commandsSvc.execute(
1525
+ record.agent,
1526
+ trimmed,
1527
+ new AbortController().signal
1528
+ );
1529
+ } catch (error) {
1530
+ return respond(`\u26A0 /${commandMatch[1]} failed: ${errorChain2(error)}`);
1531
+ }
1532
+ if (execution !== void 0) {
1533
+ const { result } = execution;
1534
+ const text = result.text ?? (result.kind === "success" ? `/${commandMatch[1]} \u2713` : `/${commandMatch[1]} failed`);
1535
+ publishCommands(params.sessionId, record);
1536
+ return respond(result.kind === "error" ? `\u26A0 ${text}` : text);
1537
+ }
1538
+ }
1539
+ }
1421
1540
  if (!await credentialPresent()) {
1422
1541
  return respond(
1423
1542
  [
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.4.1",
6
+ "version": "0.4.3",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/openma-ai/deepseek-harness-acp.git"