workspacecord 1.0.2 → 1.1.0

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.
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  config
4
- } from "./chunk-2LBNM64L.js";
4
+ } from "./chunk-D6J3X35H.js";
5
5
  import {
6
- Store,
7
6
  bindProjectCategory,
8
7
  getProjectByCategoryId,
9
8
  getProjectByName,
@@ -11,16 +10,14 @@ import {
11
10
  setProjectControlChannel,
12
11
  setProjectHistoryChannel,
13
12
  updateProject
14
- } from "./chunk-NIXZJTOZ.js";
13
+ } from "./chunk-7KESUGJP.js";
15
14
  import {
16
- isAbortError,
17
- resolvePath,
18
- sanitizeName
19
- } from "./chunk-WE4X3JB3.js";
15
+ Store
16
+ } from "./chunk-CBNENUW6.js";
20
17
 
21
18
  // src/thread-manager.ts
22
- import { existsSync as existsSync2 } from "fs";
23
- import { sep } from "path";
19
+ import "fs";
20
+ import { sep as sep2 } from "path";
24
21
 
25
22
  // src/providers/claude-provider.ts
26
23
  import { query } from "@anthropic-ai/claude-agent-sdk";
@@ -253,7 +250,7 @@ providers.set("claude", new ClaudeProvider());
253
250
  var codexLoadAttempted = false;
254
251
  async function loadCodexProvider() {
255
252
  try {
256
- const { CodexProvider } = await import("./codex-provider-7CI5W34X.js");
253
+ const { CodexProvider } = await import("./codex-provider-P7TDYK6B.js");
257
254
  providers.set("codex", new CodexProvider());
258
255
  codexLoadAttempted = true;
259
256
  } catch (err) {
@@ -396,18 +393,24 @@ async function bindMountedProjectToCategory(projectName, categoryId, categoryNam
396
393
  function setHistoryChannelId(categoryId, channelId) {
397
394
  const project = getProjectByCategoryId(categoryId);
398
395
  if (!project) return;
399
- void setProjectHistoryChannel(project.name, channelId);
396
+ setProjectHistoryChannel(project.name, channelId).catch(
397
+ (err) => console.error(`Failed to set history channel for project "${project.name}": ${err.message}`)
398
+ );
400
399
  }
401
400
  function setControlChannelId(categoryId, channelId) {
402
401
  const project = getProjectByCategoryId(categoryId);
403
402
  if (!project) return;
404
- void setProjectControlChannel(project.name, channelId);
403
+ setProjectControlChannel(project.name, channelId).catch(
404
+ (err) => console.error(`Failed to set control channel for project "${project.name}": ${err.message}`)
405
+ );
405
406
  }
406
407
  function setPersonality(categoryId, personality) {
407
408
  const project = getProjectByCategoryId(categoryId);
408
409
  if (!project) return;
409
410
  project.personality = personality;
410
- void updateProject(project);
411
+ updateProject(project).catch(
412
+ (err) => console.error(`Failed to update personality for project "${project.name}": ${err.message}`)
413
+ );
411
414
  }
412
415
  function getPersonality(categoryId) {
413
416
  return getProjectByCategoryId(categoryId)?.personality;
@@ -416,19 +419,25 @@ function clearPersonality(categoryId) {
416
419
  const project = getProjectByCategoryId(categoryId);
417
420
  if (!project) return;
418
421
  delete project.personality;
419
- void updateProject(project);
422
+ updateProject(project).catch(
423
+ (err) => console.error(`Failed to clear personality for project "${project.name}": ${err.message}`)
424
+ );
420
425
  }
421
426
  function addSkill(categoryId, name, prompt) {
422
427
  const project = getProjectByCategoryId(categoryId);
423
428
  if (!project) return;
424
429
  project.skills[name] = prompt;
425
- void updateProject(project);
430
+ updateProject(project).catch(
431
+ (err) => console.error(`Failed to add skill "${name}" for project "${project.name}": ${err.message}`)
432
+ );
426
433
  }
427
434
  function removeSkill(categoryId, name) {
428
435
  const project = getProjectByCategoryId(categoryId);
429
436
  if (!project || !project.skills[name]) return false;
430
437
  delete project.skills[name];
431
- void updateProject(project);
438
+ updateProject(project).catch(
439
+ (err) => console.error(`Failed to remove skill "${name}" for project "${project.name}": ${err.message}`)
440
+ );
432
441
  return true;
433
442
  }
434
443
  function getSkills(categoryId) {
@@ -494,33 +503,75 @@ async function writeMcpJson(projectDir, servers) {
494
503
  await writeFile(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
495
504
  }
496
505
 
497
- // src/thread-manager.ts
498
- var MODE_PROMPTS = {
499
- auto: "",
500
- plan: "You MUST use EnterPlanMode at the start of every task. Present your plan for user approval before making any code changes. Do not write or edit files until the user approves the plan.",
501
- normal: "Before performing destructive or significant operations (deleting files, running dangerous commands, making large refactors, writing to many files), use AskUserQuestion to confirm with the user first. Ask for explicit approval before proceeding with changes.",
502
- monitor: "This session is running in monitored autonomy mode. Treat the active user request as the task objective and keep working until it is fully satisfied. Do not stop at a partial implementation or ask the user for follow-up direction unless you are truly blocked by missing permissions, credentials, or required external information that you cannot obtain yourself. When you believe the task is complete, explain concisely what was finished and why it satisfies the request."
503
- };
504
- var MONITOR_SYSTEM_PROMPT = `You are a monitor agent supervising another coding agent.
505
-
506
- Your job is to judge progress against the user's original request and decide whether the worker should continue.
506
+ // src/utils.ts
507
+ import { resolve, isAbsolute } from "path";
508
+ import { homedir } from "os";
509
+ function sanitizeName(name) {
510
+ return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 50) || "session";
511
+ }
512
+ function resolvePath(p) {
513
+ if (p.startsWith("~/") || p === "~") {
514
+ return p.replace("~", homedir());
515
+ }
516
+ return isAbsolute(p) ? p : resolve(process.cwd(), p);
517
+ }
518
+ function formatDuration(ms) {
519
+ const s = Math.floor(ms / 1e3);
520
+ if (s < 60) return `${s}s`;
521
+ const m = Math.floor(s / 60);
522
+ if (m < 60) return `${m}m ${s % 60}s`;
523
+ const h = Math.floor(m / 60);
524
+ return `${h}h ${m % 60}m`;
525
+ }
526
+ function formatRelative(ts) {
527
+ const diff = Date.now() - ts;
528
+ if (diff < 6e4) return "just now";
529
+ if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
530
+ if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
531
+ return `${Math.floor(diff / 864e5)}d ago`;
532
+ }
533
+ function truncate(s, max) {
534
+ if (s.length <= max) return s;
535
+ return s.slice(0, max - 1) + "\u2026";
536
+ }
537
+ function isUserAllowed(userId, allowedUsers, allowAll) {
538
+ if (allowAll) return true;
539
+ if (allowedUsers.length === 0) return false;
540
+ return allowedUsers.includes(userId);
541
+ }
542
+ var ABORT_PATTERNS = ["abort", "cancel", "interrupt", "killed", "signal"];
543
+ function isAbortError(err) {
544
+ if (err instanceof Error && err.name === "AbortError") return true;
545
+ const msg = (err.message || "").toLowerCase();
546
+ return ABORT_PATTERNS.some((p) => msg.includes(p));
547
+ }
548
+ function formatUptime(startTime) {
549
+ const ms = Date.now() - startTime;
550
+ const seconds = Math.floor(ms / 1e3);
551
+ const minutes = Math.floor(seconds / 60);
552
+ const hours = Math.floor(minutes / 60);
553
+ const days = Math.floor(hours / 24);
554
+ if (days > 0) return `${days}d ${hours % 24}h`;
555
+ if (hours > 0) return `${hours}h ${minutes % 60}m`;
556
+ if (minutes > 0) return `${minutes}m`;
557
+ return `${seconds}s`;
558
+ }
507
559
 
508
- Return JSON only in this schema:
509
- {
510
- "status": "complete" | "continue" | "blocked",
511
- "confidence": "high" | "medium" | "low",
512
- "rationale": "Short explanation tied to the original request",
513
- "steering": "Concrete next instructions for the worker. Empty string only when status is complete.",
514
- "completionSummary": "Short summary of what is complete. Empty string unless status is complete."
560
+ // src/discord/session-message-context.ts
561
+ function buildDiscordSessionMessageContext() {
562
+ return [
563
+ "\u5F53\u524D\u4F1A\u8BDD\u4E2D\u7684\u6D88\u606F\u6765\u81EA Discord\u3002",
564
+ "\u6D88\u606F\u5305\u4E2D\u7684 [attachments] \u5757\u4F1A\u66B4\u9732 session_id\u3001message_id \u4E0E attachment_id\uFF0C\u8DB3\u4EE5\u6784\u6210 `workspacecord attachment fetch --session <session-id> --message <message-id> --attachment <attachment-id>` \u6216 `workspacecord attachment fetch --session <session-id> --message <message-id> --all`\u3002",
565
+ "\u9644\u4EF6\u9ED8\u8BA4\u4E0D\u81EA\u52A8\u4E0B\u8F7D\uFF0C\u4F60\u9996\u5148\u53EA\u4F1A\u770B\u5230\u9644\u4EF6\u6458\u8981\u3002",
566
+ "\u5982\u679C\u4F60\u9700\u8981\u67E5\u770B\u9644\u4EF6\u5185\u5BB9\uFF0C\u8BF7\u901A\u8FC7\u4E0A\u8FF0\u672C\u5730\u547D\u4EE4\u83B7\u53D6\u3002",
567
+ "\u6B63\u5E38\u56DE\u590D\u65F6\u4E0D\u9700\u8981\u53CD\u590D\u5F15\u7528\u6700\u65B0\u4E00\u6761 Discord \u6D88\u606F\u3002",
568
+ "\u957F\u4EFB\u52A1\u5B8C\u6210\u540E\uFF0C\u5FC5\u987B\u53D1\u9001\u65B0\u7684\u53EF\u89C1\u6D88\u606F\uFF0C\u4E0D\u8981\u53EA\u4F9D\u8D56\u7F16\u8F91\u65E7\u7684\u8FDB\u5EA6\u6D88\u606F\u3002"
569
+ ].join(" ");
515
570
  }
516
571
 
517
- Rules:
518
- - Favor continuing unless the task clearly satisfies the original request.
519
- - Judge against robustness, completeness, and the user's stated quality bar, not just whether some code changed.
520
- - If the worker stopped early, ask for the next concrete step instead of accepting the output.
521
- - Use "blocked" only for true blockers the worker cannot resolve autonomously.
522
- - Never ask the human for optional next steps.
523
- - Output valid JSON and nothing else.`;
572
+ // src/session-registry.ts
573
+ import { existsSync as existsSync2 } from "fs";
574
+ import { sep } from "path";
524
575
  var sessionStore = new Store("sessions.json");
525
576
  var sessions = /* @__PURE__ */ new Map();
526
577
  var idToChannelId = /* @__PURE__ */ new Map();
@@ -566,11 +617,17 @@ async function loadSessions() {
566
617
  mode: s.mode ?? "auto",
567
618
  subagentDepth: s.subagentDepth ?? 0,
568
619
  type: s.type ?? "persistent",
620
+ codexSandboxMode: s.codexSandboxMode,
621
+ codexApprovalPolicy: s.codexApprovalPolicy,
622
+ codexBypass: s.codexBypass,
623
+ codexNetworkAccessEnabled: s.codexNetworkAccessEnabled,
624
+ codexWebSearchMode: s.codexWebSearchMode,
569
625
  workflowState: s.workflowState ?? createDefaultWorkflowState(),
570
626
  currentTurn: s.currentTurn ?? 0,
571
627
  humanResolved: s.humanResolved ?? false,
572
628
  currentInteractionMessageId: s.currentInteractionMessageId,
573
629
  statusCardMessageId: s.statusCardMessageId,
630
+ lastInboundMessageId: s.lastInboundMessageId,
574
631
  discoverySource: s.discoverySource ?? "discord",
575
632
  lastObservedState: s.lastObservedState,
576
633
  lastObservedEventKey: s.lastObservedEventKey,
@@ -611,6 +668,11 @@ async function persistSessionsNow() {
611
668
  agentPersona: s.agentPersona,
612
669
  verbose: s.verbose || false,
613
670
  claudePermissionMode: s.claudePermissionMode,
671
+ codexSandboxMode: s.codexSandboxMode,
672
+ codexApprovalPolicy: s.codexApprovalPolicy,
673
+ codexBypass: s.codexBypass,
674
+ codexNetworkAccessEnabled: s.codexNetworkAccessEnabled,
675
+ codexWebSearchMode: s.codexWebSearchMode,
614
676
  monitorGoal: s.monitorGoal,
615
677
  monitorProviderSessionId: s.monitorProviderSessionId,
616
678
  workflowState: s.workflowState,
@@ -622,6 +684,7 @@ async function persistSessionsNow() {
622
684
  humanResolved: s.humanResolved,
623
685
  currentInteractionMessageId: s.currentInteractionMessageId,
624
686
  statusCardMessageId: s.statusCardMessageId,
687
+ lastInboundMessageId: s.lastInboundMessageId,
625
688
  discoverySource: s.discoverySource,
626
689
  lastObservedState: s.lastObservedState,
627
690
  lastObservedEventKey: s.lastObservedEventKey,
@@ -672,6 +735,11 @@ async function createSession(params) {
672
735
  subagentDepth = 0,
673
736
  mode = config.defaultMode,
674
737
  claudePermissionMode,
738
+ codexSandboxMode,
739
+ codexApprovalPolicy,
740
+ codexBypass,
741
+ codexNetworkAccessEnabled,
742
+ codexWebSearchMode,
675
743
  discoverySource = "discord",
676
744
  remoteHumanControl
677
745
  } = params;
@@ -679,7 +747,6 @@ async function createSession(params) {
679
747
  if (!existsSync2(resolvedDir)) {
680
748
  throw new Error(`Directory does not exist: ${resolvedDir}`);
681
749
  }
682
- await ensureProvider(provider);
683
750
  if (sessions.has(channelId)) {
684
751
  throw new Error(`Session for channelId "${channelId}" already exists`);
685
752
  }
@@ -706,6 +773,11 @@ async function createSession(params) {
706
773
  agentPersona: void 0,
707
774
  verbose: false,
708
775
  claudePermissionMode,
776
+ codexSandboxMode,
777
+ codexApprovalPolicy,
778
+ codexBypass,
779
+ codexNetworkAccessEnabled,
780
+ codexWebSearchMode,
709
781
  monitorGoal: void 0,
710
782
  monitorProviderSessionId: void 0,
711
783
  workflowState: createDefaultWorkflowState(),
@@ -718,6 +790,7 @@ async function createSession(params) {
718
790
  humanResolved: false,
719
791
  currentInteractionMessageId: void 0,
720
792
  statusCardMessageId: void 0,
793
+ lastInboundMessageId: void 0,
721
794
  discoverySource,
722
795
  remoteHumanControl
723
796
  };
@@ -767,17 +840,9 @@ function getSessionsByCategory(categoryId) {
767
840
  function getAllSessions() {
768
841
  return Array.from(sessions.values());
769
842
  }
770
- function getSessionByProviderSessionId(provider, providerSessionId) {
771
- for (const session of sessions.values()) {
772
- if (session.provider !== provider) continue;
773
- if (!session.providerSessionId) continue;
774
- if (session.providerSessionId === providerSessionId) return session;
775
- }
776
- return void 0;
777
- }
778
843
  function findCodexSessionForMonitor(providerSessionId, cwd) {
779
844
  if (providerSessionId) {
780
- const byProviderId = getSessionByProviderSessionId("codex", providerSessionId);
845
+ const byProviderId = getSessionByProviderSession("codex", providerSessionId);
781
846
  if (byProviderId) return byProviderId;
782
847
  }
783
848
  if (!cwd) return void 0;
@@ -837,6 +902,58 @@ function updateSession(sessionId, patch) {
837
902
  Object.assign(session, patch);
838
903
  debouncedSave();
839
904
  }
905
+ async function updateSessionPermissions(sessionId, patch) {
906
+ const session = getSession(sessionId);
907
+ if (!session) {
908
+ throw new Error(`Session "${sessionId}" not found`);
909
+ }
910
+ Object.assign(session, patch);
911
+ session.lastActivity = Date.now();
912
+ await saveSessionsImmediate();
913
+ }
914
+ function resolveEffectiveClaudePermissionMode(session) {
915
+ return session.mode === "auto" ? "bypass" : session.claudePermissionMode ?? config.claudePermissionMode;
916
+ }
917
+ function resolveEffectiveCodexOptions(session) {
918
+ const bypass = session.codexBypass === true;
919
+ if (bypass) {
920
+ return {
921
+ sandboxMode: "danger-full-access",
922
+ approvalPolicy: "never",
923
+ networkAccessEnabled: true,
924
+ webSearchMode: "live",
925
+ bypass: true
926
+ };
927
+ }
928
+ return {
929
+ sandboxMode: session.codexSandboxMode ?? config.codexSandboxMode,
930
+ approvalPolicy: session.mode === "auto" ? "never" : session.codexApprovalPolicy ?? config.codexApprovalPolicy,
931
+ networkAccessEnabled: session.codexNetworkAccessEnabled ?? config.codexNetworkAccessEnabled,
932
+ webSearchMode: session.codexWebSearchMode ?? config.codexWebSearchMode,
933
+ bypass: false
934
+ };
935
+ }
936
+ function getSessionPermissionSummary(session) {
937
+ if (session.provider === "claude") {
938
+ return resolveEffectiveClaudePermissionMode(session);
939
+ }
940
+ const codex = resolveEffectiveCodexOptions(session);
941
+ if (codex.bypass) return "bypass";
942
+ return `${codex.sandboxMode} | ${codex.approvalPolicy} | net:${codex.networkAccessEnabled ? "on" : "off"} | search:${codex.webSearchMode}`;
943
+ }
944
+ function getSessionPermissionDetails(session) {
945
+ if (session.provider === "claude") {
946
+ return `Claude: ${resolveEffectiveClaudePermissionMode(session)}`;
947
+ }
948
+ const codex = resolveEffectiveCodexOptions(session);
949
+ return [
950
+ `sandbox=${codex.sandboxMode}`,
951
+ `approval=${codex.approvalPolicy}`,
952
+ `bypass=${codex.bypass ? "on" : "off"}`,
953
+ `network=${codex.networkAccessEnabled ? "on" : "off"}`,
954
+ `search=${codex.webSearchMode}`
955
+ ].join(" | ");
956
+ }
840
957
  function setStatusCardBinding(sessionId, binding) {
841
958
  const session = getSession(sessionId);
842
959
  if (!session) return;
@@ -928,6 +1045,81 @@ function resetWorkflowState(sessionId) {
928
1045
  session.workflowState = createDefaultWorkflowState();
929
1046
  debouncedSave();
930
1047
  }
1048
+ function abortSession(sessionId) {
1049
+ return abortSessionWithReason(sessionId, "user");
1050
+ }
1051
+ function abortSessionWithReason(sessionId, reason) {
1052
+ const session = getSession(sessionId);
1053
+ if (!session) return false;
1054
+ const controller = sessionControllers.get(session.id);
1055
+ sessionAbortReasons.set(session.id, reason);
1056
+ if (controller) {
1057
+ controller.abort();
1058
+ }
1059
+ if (session.isGenerating) {
1060
+ session.isGenerating = false;
1061
+ sessionControllers.delete(session.id);
1062
+ debouncedSave();
1063
+ return true;
1064
+ }
1065
+ return !!controller;
1066
+ }
1067
+ function consumeAbortReason(sessionId) {
1068
+ const session = getSession(sessionId);
1069
+ if (!session) return void 0;
1070
+ const reason = sessionAbortReasons.get(session.id);
1071
+ sessionAbortReasons.delete(session.id);
1072
+ return reason;
1073
+ }
1074
+ function setSessionController(sessionId, controller) {
1075
+ sessionControllers.set(sessionId, controller);
1076
+ }
1077
+ function clearSessionController(sessionId) {
1078
+ sessionControllers.delete(sessionId);
1079
+ }
1080
+ function markSessionGenerating(sessionId, generating) {
1081
+ const session = getSession(sessionId);
1082
+ if (!session) return;
1083
+ session.isGenerating = generating;
1084
+ session.lastActivity = Date.now();
1085
+ if (!generating) {
1086
+ void saveSessionsImmediate();
1087
+ }
1088
+ }
1089
+ function saveSessionImmediate() {
1090
+ return saveSessionsImmediate();
1091
+ }
1092
+ function debouncedSaveSession() {
1093
+ debouncedSave();
1094
+ }
1095
+
1096
+ // src/thread-manager.ts
1097
+ var MODE_PROMPTS = {
1098
+ auto: "",
1099
+ plan: "You MUST use EnterPlanMode at the start of every task. Present your plan for user approval before making any code changes. Do not write or edit files until the user approves the plan.",
1100
+ normal: "Before performing destructive or significant operations (deleting files, running dangerous commands, making large refactors, writing to many files), use AskUserQuestion to confirm with the user first. Ask for explicit approval before proceeding with changes.",
1101
+ monitor: "This session is running in monitored autonomy mode. Treat the active user request as the task objective and keep working until it is fully satisfied. Do not stop at a partial implementation or ask the user for follow-up direction unless you are truly blocked by missing permissions, credentials, or required external information that you cannot obtain yourself. When you believe the task is complete, explain concisely what was finished and why it satisfies the request."
1102
+ };
1103
+ var MONITOR_SYSTEM_PROMPT = `You are a monitor agent supervising another coding agent.
1104
+
1105
+ Your job is to judge progress against the user's original request and decide whether the worker should continue.
1106
+
1107
+ Return JSON only in this schema:
1108
+ {
1109
+ "status": "complete" | "continue" | "blocked",
1110
+ "confidence": "high" | "medium" | "low",
1111
+ "rationale": "Short explanation tied to the original request",
1112
+ "steering": "Concrete next instructions for the worker. Empty string only when status is complete.",
1113
+ "completionSummary": "Short summary of what is complete. Empty string unless status is complete."
1114
+ }
1115
+
1116
+ Rules:
1117
+ - Favor continuing unless the task clearly satisfies the original request.
1118
+ - Judge against robustness, completeness, and the user's stated quality bar, not just whether some code changed.
1119
+ - If the worker stopped early, ask for the next concrete step instead of accepting the output.
1120
+ - Use "blocked" only for true blockers the worker cannot resolve autonomously.
1121
+ - Never ask the human for optional next steps.
1122
+ - Output valid JSON and nothing else.`;
931
1123
  function buildSystemPromptParts(session) {
932
1124
  const parts = [];
933
1125
  const personality = getPersonality(session.categoryId);
@@ -938,6 +1130,7 @@ function buildSystemPromptParts(session) {
938
1130
  }
939
1131
  const modePrompt = MODE_PROMPTS[session.mode];
940
1132
  if (modePrompt) parts.push(modePrompt);
1133
+ parts.push(buildDiscordSessionMessageContext());
941
1134
  return parts;
942
1135
  }
943
1136
  function buildMonitorSystemPromptParts(session) {
@@ -945,20 +1138,21 @@ function buildMonitorSystemPromptParts(session) {
945
1138
  const personality = getPersonality(session.categoryId);
946
1139
  if (personality) parts.push(personality);
947
1140
  parts.push(MONITOR_SYSTEM_PROMPT);
1141
+ parts.push(buildDiscordSessionMessageContext());
948
1142
  return parts;
949
1143
  }
950
1144
  function buildProviderOptions(session, controller, isMonitor = false, runtimeOverrides = {}) {
951
- const isAutoMode = session.mode === "auto";
1145
+ const effectiveCodex = resolveEffectiveCodexOptions(session);
952
1146
  return {
953
1147
  directory: session.directory,
954
1148
  providerSessionId: isMonitor ? session.monitorProviderSessionId : session.providerSessionId,
955
1149
  model: session.model,
956
- sandboxMode: config.codexSandboxMode,
957
- approvalPolicy: isAutoMode ? "never" : config.codexApprovalPolicy,
958
- networkAccessEnabled: config.codexNetworkAccessEnabled,
959
- webSearchMode: config.codexWebSearchMode,
1150
+ sandboxMode: effectiveCodex.sandboxMode,
1151
+ approvalPolicy: effectiveCodex.approvalPolicy,
1152
+ networkAccessEnabled: effectiveCodex.networkAccessEnabled,
1153
+ webSearchMode: effectiveCodex.webSearchMode,
960
1154
  modelReasoningEffort: config.codexReasoningEffort || void 0,
961
- claudePermissionMode: isAutoMode ? "bypass" : session.claudePermissionMode ?? config.claudePermissionMode,
1155
+ claudePermissionMode: resolveEffectiveClaudePermissionMode(session),
962
1156
  systemPromptParts: isMonitor ? buildMonitorSystemPromptParts(session) : buildSystemPromptParts(session),
963
1157
  abortController: controller,
964
1158
  canUseTool: runtimeOverrides.canUseTool
@@ -969,33 +1163,39 @@ async function* sendPrompt(sessionId, prompt, runtimeOverrides = {}) {
969
1163
  if (!session) throw new Error(`Session "${sessionId}" not found`);
970
1164
  if (session.isGenerating) throw new Error("Session is already generating");
971
1165
  const controller = new AbortController();
972
- sessionControllers.set(session.id, controller);
973
- session.isGenerating = true;
974
- session.lastActivity = Date.now();
1166
+ setSessionController(session.id, controller);
1167
+ markSessionGenerating(session.id, true);
975
1168
  const provider = await ensureProvider(session.provider);
976
1169
  try {
977
- const stream = provider.sendPrompt(prompt, buildProviderOptions(session, controller, false, runtimeOverrides));
1170
+ const stream = provider.sendPrompt(
1171
+ prompt,
1172
+ buildProviderOptions(session, controller, false, runtimeOverrides)
1173
+ );
978
1174
  for await (const event of stream) {
979
1175
  if (event.type === "session_init") {
980
- session.providerSessionId = event.providerSessionId || void 0;
981
- debouncedSave();
1176
+ const s2 = getSession(sessionId);
1177
+ if (s2) {
1178
+ s2.providerSessionId = event.providerSessionId || void 0;
1179
+ debouncedSaveSession();
1180
+ }
982
1181
  }
983
1182
  if (event.type === "result") {
984
- session.totalCost += event.costUsd;
1183
+ const s2 = getSession(sessionId);
1184
+ if (s2) s2.totalCost += event.costUsd;
985
1185
  }
986
1186
  yield event;
987
1187
  }
988
- session.messageCount++;
1188
+ const s = getSession(sessionId);
1189
+ if (s) s.messageCount++;
989
1190
  } catch (err) {
990
1191
  if (isAbortError(err)) {
991
1192
  } else {
992
1193
  throw err;
993
1194
  }
994
1195
  } finally {
995
- session.isGenerating = false;
996
- session.lastActivity = Date.now();
997
- sessionControllers.delete(session.id);
998
- await saveSessionsImmediate();
1196
+ markSessionGenerating(sessionId, false);
1197
+ clearSessionController(sessionId);
1198
+ await saveSessionImmediate();
999
1199
  }
1000
1200
  }
1001
1201
  async function* continueSession(sessionId) {
@@ -1006,9 +1206,8 @@ async function* continueSessionWithOverrides(sessionId, runtimeOverrides = {}) {
1006
1206
  if (!session) throw new Error(`Session "${sessionId}" not found`);
1007
1207
  if (session.isGenerating) throw new Error("Session is already generating");
1008
1208
  const controller = new AbortController();
1009
- sessionControllers.set(session.id, controller);
1010
- session.isGenerating = true;
1011
- session.lastActivity = Date.now();
1209
+ setSessionController(session.id, controller);
1210
+ markSessionGenerating(session.id, true);
1012
1211
  const provider = await ensureProvider(session.provider);
1013
1212
  try {
1014
1213
  const stream = provider.continueSession(
@@ -1016,106 +1215,103 @@ async function* continueSessionWithOverrides(sessionId, runtimeOverrides = {}) {
1016
1215
  );
1017
1216
  for await (const event of stream) {
1018
1217
  if (event.type === "session_init") {
1019
- session.providerSessionId = event.providerSessionId || void 0;
1020
- debouncedSave();
1218
+ const s2 = getSession(sessionId);
1219
+ if (s2) {
1220
+ s2.providerSessionId = event.providerSessionId || void 0;
1221
+ debouncedSaveSession();
1222
+ }
1021
1223
  }
1022
1224
  if (event.type === "result") {
1023
- session.totalCost += event.costUsd;
1225
+ const s2 = getSession(sessionId);
1226
+ if (s2) s2.totalCost += event.costUsd;
1024
1227
  }
1025
1228
  yield event;
1026
1229
  }
1027
- session.messageCount++;
1230
+ const s = getSession(sessionId);
1231
+ if (s) s.messageCount++;
1028
1232
  } catch (err) {
1029
1233
  if (isAbortError(err)) {
1030
1234
  } else {
1031
1235
  throw err;
1032
1236
  }
1033
1237
  } finally {
1034
- session.isGenerating = false;
1035
- session.lastActivity = Date.now();
1036
- sessionControllers.delete(session.id);
1037
- await saveSessionsImmediate();
1238
+ markSessionGenerating(sessionId, false);
1239
+ clearSessionController(sessionId);
1240
+ await saveSessionImmediate();
1038
1241
  }
1039
1242
  }
1040
1243
  async function* sendMonitorPrompt(sessionId, prompt) {
1041
1244
  const session = getSession(sessionId);
1042
1245
  if (!session) throw new Error(`Session "${sessionId}" not found`);
1043
1246
  const provider = await ensureProvider(session.provider);
1044
- session.lastActivity = Date.now();
1247
+ const s = getSession(sessionId);
1248
+ if (s) s.lastActivity = Date.now();
1045
1249
  const controller = new AbortController();
1046
1250
  const stream = provider.sendPrompt(prompt, buildProviderOptions(session, controller, true));
1047
1251
  for await (const event of stream) {
1048
1252
  if (event.type === "session_init") {
1049
- session.monitorProviderSessionId = event.providerSessionId || void 0;
1050
- debouncedSave();
1253
+ const cur2 = getSession(sessionId);
1254
+ if (cur2) {
1255
+ cur2.monitorProviderSessionId = event.providerSessionId || void 0;
1256
+ debouncedSaveSession();
1257
+ }
1051
1258
  }
1052
1259
  if (event.type === "result") {
1053
- session.totalCost += event.costUsd;
1260
+ const cur2 = getSession(sessionId);
1261
+ if (cur2) cur2.totalCost += event.costUsd;
1054
1262
  }
1055
1263
  yield event;
1056
1264
  }
1057
- session.lastActivity = Date.now();
1058
- debouncedSave();
1265
+ const cur = getSession(sessionId);
1266
+ if (cur) cur.lastActivity = Date.now();
1267
+ debouncedSaveSession();
1059
1268
  }
1060
- function abortSession(sessionId) {
1061
- return abortSessionWithReason(sessionId, "user");
1062
- }
1063
- function abortSessionWithReason(sessionId, reason) {
1064
- const session = getSession(sessionId);
1065
- if (!session) return false;
1066
- const controller = sessionControllers.get(session.id);
1067
- sessionAbortReasons.set(session.id, reason);
1068
- if (controller) {
1069
- controller.abort();
1070
- }
1071
- if (session.isGenerating) {
1072
- session.isGenerating = false;
1073
- sessionControllers.delete(session.id);
1074
- debouncedSave();
1075
- return true;
1076
- }
1077
- return !!controller;
1078
- }
1079
- function consumeAbortReason(sessionId) {
1080
- const session = getSession(sessionId);
1081
- if (!session) return void 0;
1082
- const reason = sessionAbortReasons.get(session.id);
1083
- sessionAbortReasons.delete(session.id);
1084
- return reason;
1269
+ function buildClaudeSubagentProviderSessionId(parentProviderSessionId, agentId) {
1270
+ return `subagent:${parentProviderSessionId}:${agentId}`;
1085
1271
  }
1086
1272
  function updateLocalObservation(sessionId, patch) {
1087
- const observationPatch = {
1273
+ updateSession(sessionId, {
1088
1274
  discoverySource: patch.discoverySource,
1089
1275
  lastObservedAt: Date.now(),
1090
- lastObservedCwd: resolvePath(patch.cwd)
1091
- };
1092
- if (patch.remoteHumanControl !== void 0) {
1093
- observationPatch.remoteHumanControl = patch.remoteHumanControl;
1094
- }
1095
- updateSession(sessionId, observationPatch);
1276
+ lastObservedCwd: resolvePath(patch.cwd),
1277
+ ...patch.remoteHumanControl !== void 0 ? { remoteHumanControl: patch.remoteHumanControl } : {}
1278
+ });
1096
1279
  }
1097
1280
  async function registerLocalSession(params, guild) {
1098
- const { provider, providerSessionId, cwd, discoverySource, labelHint, remoteHumanControl } = params;
1099
- const existing = getSessionByProviderSessionId(provider, providerSessionId);
1281
+ const {
1282
+ provider,
1283
+ providerSessionId,
1284
+ cwd,
1285
+ discoverySource,
1286
+ labelHint,
1287
+ remoteHumanControl,
1288
+ subagent
1289
+ } = params;
1290
+ const effectiveProviderSessionId = provider === "claude" && subagent?.parentProviderSessionId && subagent.agentId ? buildClaudeSubagentProviderSessionId(subagent.parentProviderSessionId, subagent.agentId) : providerSessionId;
1291
+ const effectiveAgentLabel = subagent?.agentType || labelHint || effectiveProviderSessionId.slice(0, 12);
1292
+ const { isArchivedProviderSession } = await import("./archive-manager-KMNAWQIN.js");
1293
+ if (isArchivedProviderSession(provider, effectiveProviderSessionId)) {
1294
+ console.log(
1295
+ `[registerLocalSession] Skip archived ${provider} session ${effectiveProviderSessionId} (source: ${discoverySource})`
1296
+ );
1297
+ return null;
1298
+ }
1299
+ const existing = getSessionByProviderSession(provider, effectiveProviderSessionId);
1100
1300
  if (existing) {
1101
- updateLocalObservation(existing.id, {
1102
- discoverySource,
1103
- cwd,
1104
- remoteHumanControl
1105
- });
1301
+ updateLocalObservation(existing.id, { discoverySource, cwd, remoteHumanControl });
1106
1302
  return { session: existing, isNewlyCreated: false };
1107
1303
  }
1108
- const { getProjectByPath, getAllRegisteredProjects: getAllRegisteredProjects2 } = await import("./project-registry-DQT5ORUU.js");
1109
- const { resolvePath: resolvePath2 } = await import("./utils-72GMT2X5.js");
1110
- const normalizedCwd = resolvePath2(cwd);
1304
+ const { getProjectByPath, getAllRegisteredProjects: getAllRegisteredProjects2 } = await import("./project-registry-LL75XEUV.js");
1305
+ const { ChannelType, ThreadAutoArchiveDuration } = await import("discord.js");
1306
+ const normalizedCwd = resolvePath(cwd);
1111
1307
  let project = getProjectByPath(normalizedCwd);
1112
1308
  if (!project) {
1113
1309
  const allProjects = getAllRegisteredProjects2();
1114
1310
  let bestMatch;
1115
1311
  let bestMatchPathLength = -1;
1116
1312
  for (const p of allProjects) {
1117
- const projectPath = resolvePath2(p.path);
1118
- if (normalizedCwd.startsWith(projectPath + sep) && projectPath.length > bestMatchPathLength) {
1313
+ const projectPath = resolvePath(p.path);
1314
+ if (normalizedCwd.startsWith(projectPath + sep2) && projectPath.length > bestMatchPathLength) {
1119
1315
  bestMatch = p;
1120
1316
  bestMatchPathLength = projectPath.length;
1121
1317
  }
@@ -1128,7 +1324,6 @@ async function registerLocalSession(params, guild) {
1128
1324
  );
1129
1325
  return null;
1130
1326
  }
1131
- const { ChannelType } = await import("discord.js");
1132
1327
  const category = guild.channels.cache.get(project.discordCategoryId);
1133
1328
  if (!category || category.type !== ChannelType.GuildCategory) {
1134
1329
  console.warn(
@@ -1136,26 +1331,73 @@ async function registerLocalSession(params, guild) {
1136
1331
  );
1137
1332
  return null;
1138
1333
  }
1139
- const base = labelHint ? labelHint.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60) : providerSessionId.slice(0, 12);
1334
+ if (subagent?.parentProviderSessionId) {
1335
+ const parentSession = getSessionByProviderSession(provider, subagent.parentProviderSessionId);
1336
+ if (!parentSession) {
1337
+ console.warn(
1338
+ `[registerLocalSession] Delaying subagent ${provider} session ${providerSessionId}: parent provider session ${subagent.parentProviderSessionId} not registered yet`
1339
+ );
1340
+ return null;
1341
+ }
1342
+ const parentChannel = guild.channels.cache.get(parentSession.channelId);
1343
+ const threadHostChannel = parentChannel?.type === ChannelType.GuildText ? parentChannel : parentChannel?.isThread?.() || parentChannel?.type === ChannelType.PublicThread ? parentChannel.parent : void 0;
1344
+ if (threadHostChannel?.type !== ChannelType.GuildText) {
1345
+ console.warn(
1346
+ `[registerLocalSession] Delaying subagent ${provider} session ${providerSessionId}: parent channel ${parentSession.channelId} is unavailable`
1347
+ );
1348
+ return null;
1349
+ }
1350
+ const normalizedThreadName = `[sub:${provider}] ${effectiveAgentLabel}`.slice(0, 100);
1351
+ const thread = await threadHostChannel.threads.create({
1352
+ name: normalizedThreadName,
1353
+ type: ChannelType.PublicThread,
1354
+ autoArchiveDuration: ThreadAutoArchiveDuration.OneHour,
1355
+ reason: `Auto-registered subagent session ${effectiveProviderSessionId}`
1356
+ });
1357
+ const session2 = await createSession({
1358
+ channelId: thread.id,
1359
+ categoryId: parentSession.categoryId,
1360
+ projectName: parentSession.projectName,
1361
+ agentLabel: effectiveAgentLabel,
1362
+ provider,
1363
+ providerSessionId: effectiveProviderSessionId,
1364
+ directory: normalizedCwd,
1365
+ type: "subagent",
1366
+ parentChannelId: parentSession.type === "subagent" ? parentSession.parentChannelId ?? threadHostChannel.id : parentSession.channelId,
1367
+ subagentDepth: Math.max(1, subagent.depth ?? parentSession.subagentDepth + 1),
1368
+ discoverySource,
1369
+ remoteHumanControl: remoteHumanControl ?? false
1370
+ });
1371
+ updateLocalObservation(session2.id, {
1372
+ discoverySource,
1373
+ cwd: normalizedCwd,
1374
+ remoteHumanControl: remoteHumanControl ?? false
1375
+ });
1376
+ console.log(
1377
+ `[registerLocalSession] Registered subagent ${provider} session ${effectiveProviderSessionId} (source: ${discoverySource}, parent: ${parentSession.channelId}, thread: ${thread.id})`
1378
+ );
1379
+ return { session: session2, isNewlyCreated: true };
1380
+ }
1381
+ const base = labelHint ? labelHint.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60) : effectiveProviderSessionId.slice(0, 12);
1140
1382
  const channelName = `${provider}-${base}`.slice(0, 100);
1141
1383
  let channel = category.children.cache.find(
1142
- (ch) => ch.type === ChannelType.GuildText && typeof ch.topic === "string" && ch.topic.includes(`Provider Session: ${providerSessionId}`)
1384
+ (ch) => ch.type === ChannelType.GuildText && typeof ch.topic === "string" && ch.topic.includes(`Provider Session: ${effectiveProviderSessionId}`)
1143
1385
  );
1144
1386
  if (!channel) {
1145
1387
  channel = await guild.channels.create({
1146
1388
  name: channelName,
1147
1389
  type: ChannelType.GuildText,
1148
1390
  parent: category.id,
1149
- topic: `${provider} session (local) | Provider Session: ${providerSessionId}`
1391
+ topic: `${provider} session (local) | Provider Session: ${effectiveProviderSessionId}`
1150
1392
  });
1151
1393
  }
1152
1394
  const session = await createSession({
1153
1395
  channelId: channel.id,
1154
1396
  categoryId: project.discordCategoryId,
1155
1397
  projectName: project.name,
1156
- agentLabel: labelHint || providerSessionId.slice(0, 12),
1398
+ agentLabel: effectiveAgentLabel,
1157
1399
  provider,
1158
- providerSessionId,
1400
+ providerSessionId: effectiveProviderSessionId,
1159
1401
  directory: normalizedCwd,
1160
1402
  type: "persistent",
1161
1403
  discoverySource,
@@ -1167,7 +1409,7 @@ async function registerLocalSession(params, guild) {
1167
1409
  remoteHumanControl: remoteHumanControl ?? false
1168
1410
  });
1169
1411
  console.log(
1170
- `[registerLocalSession] Registered ${provider} session ${providerSessionId} (source: ${discoverySource}, channel: ${channel.id})`
1412
+ `[registerLocalSession] Registered ${provider} session ${effectiveProviderSessionId} (source: ${discoverySource}, channel: ${channel.id})`
1171
1413
  );
1172
1414
  return { session, isNewlyCreated: true };
1173
1415
  }
@@ -1187,6 +1429,12 @@ export {
1187
1429
  addMcpServer,
1188
1430
  removeMcpServer,
1189
1431
  getMcpServers,
1432
+ formatDuration,
1433
+ formatRelative,
1434
+ truncate,
1435
+ isUserAllowed,
1436
+ isAbortError,
1437
+ formatUptime,
1190
1438
  loadSessions,
1191
1439
  createSession,
1192
1440
  getSession,
@@ -1196,12 +1444,16 @@ export {
1196
1444
  getSessionByProviderSession,
1197
1445
  getSessionsByCategory,
1198
1446
  getAllSessions,
1199
- getSessionByProviderSessionId,
1200
1447
  findCodexSessionForMonitor,
1201
1448
  findCodexSessionByProviderSessionId,
1202
1449
  findCodexSessionByCwd,
1203
1450
  resolveCodexSessionFromMonitor,
1204
1451
  updateSession,
1452
+ updateSessionPermissions,
1453
+ resolveEffectiveClaudePermissionMode,
1454
+ resolveEffectiveCodexOptions,
1455
+ getSessionPermissionSummary,
1456
+ getSessionPermissionDetails,
1205
1457
  setStatusCardBinding,
1206
1458
  setCurrentInteractionMessage,
1207
1459
  endSession,
@@ -1212,13 +1464,14 @@ export {
1212
1464
  setMonitorGoal,
1213
1465
  updateWorkflowState,
1214
1466
  resetWorkflowState,
1467
+ abortSession,
1468
+ abortSessionWithReason,
1469
+ consumeAbortReason,
1215
1470
  sendPrompt,
1216
1471
  continueSession,
1217
1472
  continueSessionWithOverrides,
1218
1473
  sendMonitorPrompt,
1219
- abortSession,
1220
- abortSessionWithReason,
1221
- consumeAbortReason,
1474
+ buildClaudeSubagentProviderSessionId,
1222
1475
  updateLocalObservation,
1223
1476
  registerLocalSession
1224
1477
  };