@norman-else/dsh-claude 0.1.5 → 0.1.7

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/lib/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { a as latestClaudeTasks, b as isClaudePresetId, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_CODE_PRESET_ID, g as CLAUDE_PROJECTION_PATH, h as CLAUDE_DOCTOR_PATH, i as latestClaudeSessionBinding, l as redactText, m as CLAUDE_CODE_PROVIDER_IDS, n as currentClaudeActivityCursor, o as normalizeActivity, p as CLAUDE_CODE_PROVIDER, r as latestClaudeContextUsage, s as normalizeContextUsage, t as boundText, u as safeDetail, y as TASK_TOOL_NAMES } from "./events-qlmU1KrH.mjs";
2
- import { a as ClaudeCommandBridge, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-DbfG-9KQ.mjs";
3
- import { a as resolveClaudeExecutable, o as runClaudeDoctor, r as removeManagedPreset, t as ManagedPresetConflictError } from "./preset-installer-D8q3_-wJ.mjs";
1
+ import { a as latestClaudeTasks, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_CODE_PRESET_ID, g as CLAUDE_PROJECTION_PATH, h as CLAUDE_DOCTOR_PATH, i as latestClaudeSessionBinding, l as redactText, m as CLAUDE_CODE_PROVIDER_IDS, n as currentClaudeActivityCursor, o as normalizeActivity, p as CLAUDE_CODE_PROVIDER, r as latestClaudeContextUsage, s as normalizeContextUsage, t as boundText, u as safeDetail, v as TASK_TOOL_NAMES } from "./events-DP0Ov_Re.mjs";
2
+ import { a as ClaudeCommandBridge, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-YMR2WMjn.mjs";
3
+ import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-BKw-SdOm.mjs";
4
4
  import z from "@deepseek-ai/schemastery";
5
5
  import { CallId, LlmAdapter, ReasoningEffortId, createToolResultMessage, createUserMessage } from "@deepseek-ai/dsh-llm";
6
6
  import { randomUUID } from "node:crypto";
@@ -45,6 +45,7 @@ const ACTIVITY_KINDS = /* @__PURE__ */ new Set([
45
45
  "tool-call",
46
46
  "tool-result",
47
47
  "permission",
48
+ "question",
48
49
  "subagent",
49
50
  "usage",
50
51
  "warning",
@@ -336,8 +337,14 @@ function mapApprovalOutcome(outcome, input, toolUseID) {
336
337
  decisionClassification: "user_reject"
337
338
  };
338
339
  }
339
- function createPermissionBridge(approval, activeContext) {
340
+ function createPermissionBridge(approval, activeContext, userQuestion) {
340
341
  return async (toolName, input, options) => {
342
+ if (toolName === "AskUserQuestion") return userQuestion === void 0 ? {
343
+ behavior: "deny",
344
+ message: "DeepSeek Harness user questions are unavailable; the question was cancelled.",
345
+ toolUseID: options.toolUseID,
346
+ decisionClassification: "user_reject"
347
+ } : userQuestion(input, options);
341
348
  const active = activeContext();
342
349
  if (active === void 0) return {
343
350
  behavior: "deny",
@@ -357,21 +364,24 @@ function createPermissionBridge(approval, activeContext) {
357
364
  summary: options.title ?? options.description ?? reason,
358
365
  detail: input
359
366
  });
360
- const outcome = await approval.request({
367
+ const alreadyFullAccess = await active.hasFullAccess?.() === true;
368
+ const outcome = alreadyFullAccess ? "allowed-once" : await approval.request({
361
369
  agent: active.agent,
362
370
  toolName,
363
371
  reason,
364
372
  signal: options.signal
365
373
  });
366
- const result = mapApprovalOutcome(outcome, input, options.toolUseID);
374
+ const fullAccess = alreadyFullAccess || await active.hasFullAccess?.() === true;
375
+ const effectiveOutcome = fullAccess ? "allowed-once" : outcome;
376
+ const result = mapApprovalOutcome(effectiveOutcome, input, options.toolUseID);
367
377
  if (result.behavior === "deny") active.recordDenial?.(options.toolUseID);
368
378
  await active.appendActivity({
369
379
  kind: "permission",
370
- phase: outcome === "allowed-once" ? "completed" : "denied",
380
+ phase: effectiveOutcome === "allowed-once" ? "completed" : "denied",
371
381
  toolUseId: options.toolUseID,
372
382
  toolName,
373
383
  title: options.displayName ?? toolName,
374
- summary: outcome === "allowed-once" ? "Allowed once in DeepSeek Harness" : denialMessage(outcome)
384
+ summary: fullAccess ? "Allowed by Full access in DeepSeek Harness" : effectiveOutcome === "allowed-once" ? "Allowed once in DeepSeek Harness" : denialMessage(effectiveOutcome)
375
385
  });
376
386
  return result;
377
387
  } catch (error) {
@@ -398,6 +408,120 @@ function createPermissionBridge(approval, activeContext) {
398
408
  };
399
409
  }
400
410
  //#endregion
411
+ //#region src/user-question.ts
412
+ const FAILURE_MESSAGE = "DeepSeek Harness could not collect an answer; the question was cancelled.";
413
+ const INVALID_MESSAGE = "Claude Code sent an invalid user-question request; the question was cancelled.";
414
+ function failureMessage(error) {
415
+ const code = error !== null && typeof error === "object" && "code" in error ? error.code : void 0;
416
+ return typeof code === "string" && /^[A-Z][A-Z0-9_]*$/.test(code) ? `${FAILURE_MESSAGE} (${code})` : FAILURE_MESSAGE;
417
+ }
418
+ function deny(message, toolUseID) {
419
+ return {
420
+ behavior: "deny",
421
+ message,
422
+ toolUseID,
423
+ decisionClassification: "user_reject"
424
+ };
425
+ }
426
+ function optionalText(value) {
427
+ return typeof value === "string" && value.length > 0 ? value : void 0;
428
+ }
429
+ function parseQuestions(input, toolUseID) {
430
+ if (!Array.isArray(input.questions) || input.questions.length === 0 || input.questions.length > 20) return void 0;
431
+ const seen = /* @__PURE__ */ new Set();
432
+ const questions = [];
433
+ for (const [index, value] of input.questions.entries()) {
434
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
435
+ const item = value;
436
+ if (typeof item.question !== "string" || item.question.length === 0 || seen.has(item.question)) return void 0;
437
+ seen.add(item.question);
438
+ if (item.options !== void 0 && !Array.isArray(item.options)) return void 0;
439
+ const options = (item.options ?? []).map((option) => {
440
+ if (option === null || typeof option !== "object" || Array.isArray(option)) return void 0;
441
+ const record = option;
442
+ if (typeof record.label !== "string" || record.label.length === 0) return void 0;
443
+ const description = optionalText(record.description);
444
+ return {
445
+ label: record.label,
446
+ ...description === void 0 ? {} : { description }
447
+ };
448
+ });
449
+ if (options.some((option) => option === void 0)) return void 0;
450
+ const header = optionalText(item.header);
451
+ questions.push({
452
+ id: `${toolUseID}:${index}`,
453
+ question: item.question,
454
+ ...header === void 0 ? {} : { header },
455
+ options,
456
+ multiSelect: item.multiSelect === true
457
+ });
458
+ }
459
+ return questions;
460
+ }
461
+ function answerText(answer, multiSelect) {
462
+ if (answer === void 0) return "";
463
+ const custom = optionalText(answer.custom);
464
+ if (!multiSelect && custom !== void 0) return custom;
465
+ return [...answer.selected, ...custom === void 0 ? [] : [custom]].join(", ");
466
+ }
467
+ function createUserQuestionBridge(userQuestions, activeContext) {
468
+ return async (input, options) => {
469
+ const active = activeContext();
470
+ if (active === void 0) return deny("No active DeepSeek Harness turn owns this Claude Code question.", options.toolUseID);
471
+ const questions = parseQuestions(input, options.toolUseID);
472
+ if (questions === void 0) return deny(INVALID_MESSAGE, options.toolUseID);
473
+ active.markActivity?.();
474
+ try {
475
+ await active.appendActivity({
476
+ kind: "question",
477
+ phase: "started",
478
+ toolUseId: options.toolUseID,
479
+ toolName: "AskUserQuestion",
480
+ title: "Claude asked a question",
481
+ summary: questions.length === 1 ? questions[0].question : `Claude asked ${questions.length} questions`
482
+ });
483
+ const response = await userQuestions.ask({
484
+ questions,
485
+ agent: active.agent,
486
+ signal: options.signal
487
+ });
488
+ const answersById = new Map(response.answers.map((answer) => [answer.id, answer]));
489
+ const answers = Object.fromEntries(questions.map((question) => [question.question, answerText(answersById.get(question.id), question.multiSelect === true)]));
490
+ await active.appendActivity({
491
+ kind: "question",
492
+ phase: "completed",
493
+ toolUseId: options.toolUseID,
494
+ toolName: "AskUserQuestion",
495
+ title: "Claude asked a question",
496
+ summary: "Answered in DeepSeek Harness"
497
+ });
498
+ return {
499
+ behavior: "allow",
500
+ updatedInput: {
501
+ ...input,
502
+ answers
503
+ },
504
+ toolUseID: options.toolUseID,
505
+ decisionClassification: "user_temporary"
506
+ };
507
+ } catch (error) {
508
+ const message = failureMessage(error);
509
+ try {
510
+ await active.appendActivity({
511
+ kind: "question",
512
+ phase: "failed",
513
+ toolUseId: options.toolUseID,
514
+ toolName: "AskUserQuestion",
515
+ title: "Claude asked a question",
516
+ summary: message,
517
+ isError: true
518
+ });
519
+ } catch {}
520
+ return deny(message, options.toolUseID);
521
+ }
522
+ };
523
+ }
524
+ //#endregion
401
525
  //#region src/sdk-messages.ts
402
526
  function record(value) {
403
527
  return value !== null && typeof value === "object" ? value : void 0;
@@ -905,6 +1029,7 @@ var ClaudeSupervisor = class {
905
1029
  #entries = /* @__PURE__ */ new Map();
906
1030
  #runtime;
907
1031
  #approval;
1032
+ #userQuestions;
908
1033
  #config;
909
1034
  #queryFactory;
910
1035
  #runDetached;
@@ -916,6 +1041,7 @@ var ClaudeSupervisor = class {
916
1041
  constructor(dependencies) {
917
1042
  this.#runtime = dependencies.runtime;
918
1043
  this.#approval = dependencies.approval;
1044
+ this.#userQuestions = dependencies.userQuestions;
919
1045
  this.#config = dependencies.config;
920
1046
  this.#queryFactory = dependencies.queryFactory ?? ((params) => query(params));
921
1047
  this.#runDetached = dependencies.runDetached ?? ((operation) => operation());
@@ -1151,7 +1277,7 @@ var ClaudeSupervisor = class {
1151
1277
  taskSnapshotAt: 0,
1152
1278
  taskSnapshotTimer: void 0
1153
1279
  };
1154
- const canUseTool = createPermissionBridge(this.#approval, () => {
1280
+ const activeInteraction = () => {
1155
1281
  const active = entry.active;
1156
1282
  return active === void 0 ? void 0 : {
1157
1283
  agent: active.agent,
@@ -1162,9 +1288,15 @@ var ClaudeSupervisor = class {
1162
1288
  recordDenial: (toolUseId) => {
1163
1289
  active.deniedToolUseIds.add(toolUseId);
1164
1290
  },
1291
+ hasFullAccess: async () => {
1292
+ await this.#syncPermissionMode(entry);
1293
+ return entry.permissionMode === "bypassPermissions";
1294
+ },
1165
1295
  appendActivity: (activity) => this.#appendActivity(active, activity)
1166
1296
  };
1167
- });
1297
+ };
1298
+ const userQuestion = createUserQuestionBridge(this.#userQuestions, activeInteraction);
1299
+ const canUseTool = createPermissionBridge(this.#approval, activeInteraction, userQuestion);
1168
1300
  const options = {
1169
1301
  pathToClaudeCodeExecutable: this.#config.executablePath,
1170
1302
  cwd,
@@ -1347,6 +1479,12 @@ var ClaudeSupervisor = class {
1347
1479
  title: message.toolName,
1348
1480
  summary: message.summary
1349
1481
  });
1482
+ if (!TASK_TOOL_NAMES.has(active.callNames.get(message.toolUseId) ?? message.toolName)) await this.#appendNativeToolResult(active, {
1483
+ kind: "tool-result",
1484
+ toolUseId: message.toolUseId,
1485
+ output: message.summary,
1486
+ isError: true
1487
+ });
1350
1488
  return;
1351
1489
  }
1352
1490
  }
@@ -1803,7 +1941,7 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
1803
1941
  async *stream(options) {
1804
1942
  if (options.purpose !== void 0) throw new Error(`dsh-claude: auxiliary ${options.purpose} calls are not routed into the Claude session`);
1805
1943
  const agent = resolveAgent(this.#agents, options);
1806
- if (!isClaudePresetId(this.#presetIdFor(agent))) throw new Error(`dsh-claude: provider ${CLAUDE_CODE_PROVIDER} is available only to the ${CLAUDE_CODE_PRESET_ID} preset`);
1944
+ if (this.#presetIdFor(agent) !== "claude") throw new Error(`dsh-claude: provider ${CLAUDE_CODE_PROVIDER} is available only to the ${CLAUDE_CODE_PRESET_ID} preset`);
1807
1945
  const thinkingMode = thinkingModeFor(options.reasoningEffort);
1808
1946
  const prompt = extractDirectUserText(options.messages);
1809
1947
  const events = await this.#supervisor.runTurn({
@@ -2068,7 +2206,8 @@ const inject = [
2068
2206
  "agentPresets",
2069
2207
  "commands",
2070
2208
  "subprocess",
2071
- "approval"
2209
+ "approval",
2210
+ "userQuestions"
2072
2211
  ];
2073
2212
  const Config = z.object({
2074
2213
  executablePath: z.string().default(""),
@@ -2082,7 +2221,7 @@ const SCOPE_RETRY_MS = 500;
2082
2221
  const MAX_CATALOG_RETRIES = 3;
2083
2222
  const MAX_SCOPE_RETRIES = 24;
2084
2223
  function mountClaudeMetadata(ctx, supervisor, agent, model, sidecar, resolveCommands = () => ctx.agentPresets.serviceFor(agent, CLAUDE_COMMANDS_SERVICE)) {
2085
- if (!isClaudePresetId(ctx.agentPresets.composedPreset(agent.ctx))) return void 0;
2224
+ if (ctx.agentPresets.composedPreset(agent.ctx) !== "claude") return void 0;
2086
2225
  let stopped = false;
2087
2226
  let pending = Promise.resolve();
2088
2227
  let commandScope;
@@ -2179,13 +2318,17 @@ function mountClaudeMetadata(ctx, supervisor, agent, model, sidecar, resolveComm
2179
2318
  };
2180
2319
  }, "dsh-claude: agent metadata bridge");
2181
2320
  }
2182
- async function apply(ctx, config) {
2321
+ async function installManagedPresetCompatibility(logger, install = ensureManagedPreset) {
2183
2322
  try {
2184
- await removeManagedPreset();
2323
+ return await install();
2185
2324
  } catch (error) {
2186
2325
  if (!(error instanceof ManagedPresetConflictError)) throw error;
2187
- ctx.logger.warn(`dsh-claude: preserving user-modified legacy preset at ${error.path}`);
2326
+ logger.warn(`dsh-claude: preserving user-modified preset at ${error.path}`);
2327
+ return "conflict";
2188
2328
  }
2329
+ }
2330
+ async function apply(ctx, config) {
2331
+ await installManagedPresetCompatibility(ctx.logger);
2189
2332
  const supervisorConfig = {
2190
2333
  executablePath: "",
2191
2334
  defaultModel: config.model ?? "default",
@@ -2196,6 +2339,7 @@ async function apply(ctx, config) {
2196
2339
  const supervisor = new ClaudeSupervisor({
2197
2340
  runtime: ctx.subprocess,
2198
2341
  approval: ctx.approval,
2342
+ userQuestions: ctx.userQuestions,
2199
2343
  config: supervisorConfig,
2200
2344
  runDetached: (operation) => ctx.agents.withoutInitiator(operation),
2201
2345
  sidecar
@@ -2243,7 +2387,7 @@ async function apply(ctx, config) {
2243
2387
  });
2244
2388
  const onPresetSelected = ctx.on;
2245
2389
  const stopSelected = onPresetSelected("agent-preset/selected", (sessionId, preset) => {
2246
- if (!isClaudePresetId(preset)) return;
2390
+ if (preset !== "claude") return;
2247
2391
  const agent = ctx.agents.get(sessionId);
2248
2392
  if (agent !== void 0) mountWhenPresetSettles(agent);
2249
2393
  });
@@ -2267,11 +2411,11 @@ async function apply(ctx, config) {
2267
2411
  registerClaudeDoctorRoutes(webCtx, webCtx.subprocess, supervisor, supervisorConfig, resolutionError);
2268
2412
  registerClaudeProjectionRoute(webCtx, sidecar, (sessionId) => {
2269
2413
  const agent = webCtx.agents.get(sessionId);
2270
- return agent !== void 0 && isClaudePresetId(webCtx.agentPresets.composedPreset(agent.ctx));
2414
+ return agent !== void 0 && webCtx.agentPresets.composedPreset(agent.ctx) === "claude";
2271
2415
  });
2272
2416
  });
2273
2417
  }
2274
2418
  //#endregion
2275
- export { Config, apply, inject, mountClaudeMetadata, name };
2419
+ export { Config, apply, inject, installManagedPresetCompatibility, mountClaudeMetadata, name };
2276
2420
 
2277
2421
  //# sourceMappingURL=index.mjs.map