@skj1724/oh-my-opencode 3.19.3 → 3.19.4

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/cli/index.js CHANGED
@@ -6117,6 +6117,53 @@ var init_model_resolver = __esm(() => {
6117
6117
  init_model_availability();
6118
6118
  });
6119
6119
 
6120
+ // src/shared/perf-timer.ts
6121
+ class PerfTimer {
6122
+ marks = new Map;
6123
+ spans = [];
6124
+ mark(name) {
6125
+ this.marks.set(name, performance.now());
6126
+ }
6127
+ measure(name, startMark, endMark) {
6128
+ const start = this.marks.get(startMark);
6129
+ if (start === undefined)
6130
+ return 0;
6131
+ const end = endMark ? this.marks.get(endMark) ?? performance.now() : performance.now();
6132
+ const duration = end - start;
6133
+ this.spans.push({ name, durationMs: duration, start, end });
6134
+ return duration;
6135
+ }
6136
+ getReport() {
6137
+ return [...this.spans];
6138
+ }
6139
+ reset() {
6140
+ this.marks.clear();
6141
+ this.spans = [];
6142
+ }
6143
+ static formatDuration(start, end, options) {
6144
+ const ms = (end ?? new Date).getTime() - start.getTime();
6145
+ const absMs = Math.abs(ms);
6146
+ const totalSeconds = absMs / 1000;
6147
+ const seconds = Math.floor(totalSeconds);
6148
+ const minutes = Math.floor(seconds / 60);
6149
+ const hours = Math.floor(minutes / 60);
6150
+ const precision = options?.precision ?? "full";
6151
+ if (hours > 0) {
6152
+ if (precision === "compact") {
6153
+ return `${hours}h ${minutes % 60}m`;
6154
+ }
6155
+ return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
6156
+ }
6157
+ if (minutes > 0) {
6158
+ return `${minutes}m ${seconds % 60}s`;
6159
+ }
6160
+ if (seconds === 0 && absMs > 0) {
6161
+ return `${(absMs / 1000).toFixed(1)}s`;
6162
+ }
6163
+ return `${seconds}s`;
6164
+ }
6165
+ }
6166
+
6120
6167
  // src/shared/index.ts
6121
6168
  var init_shared = __esm(() => {
6122
6169
  init_frontmatter();
@@ -8375,7 +8422,7 @@ var import_picocolors2 = __toESM(require_picocolors(), 1);
8375
8422
  // package.json
8376
8423
  var package_default = {
8377
8424
  name: "@skj1724/oh-my-opencode",
8378
- version: "3.19.3",
8425
+ version: "3.19.4",
8379
8426
  description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
8380
8427
  main: "dist/index.js",
8381
8428
  types: "dist/index.d.ts",
@@ -1,3 +1,4 @@
1
1
  export * from "./types";
2
2
  export { BackgroundManager } from "./manager";
3
3
  export { ConcurrencyManager } from "./concurrency";
4
+ export { PerformanceAggregator } from "./perf-aggregator";
@@ -25,6 +25,7 @@ export declare class BackgroundManager {
25
25
  private concurrencyManager;
26
26
  private shutdownTriggered;
27
27
  private config?;
28
+ private perfAggregator;
28
29
  private queuesByKey;
29
30
  private processingKeys;
30
31
  constructor(ctx: PluginInput, config?: BackgroundTaskConfig);
@@ -89,7 +90,6 @@ export declare class BackgroundManager {
89
90
  */
90
91
  private tryCompleteTask;
91
92
  private notifyParentSession;
92
- private formatDuration;
93
93
  private hasRunningTasks;
94
94
  private pruneStaleTasksAndNotifications;
95
95
  private checkAndInterruptStaleTasks;
@@ -0,0 +1,26 @@
1
+ import type { BackgroundTask } from "./types";
2
+ export interface AgentStat {
3
+ count: number;
4
+ avgMs: number;
5
+ p50Ms: number;
6
+ p95Ms: number;
7
+ totalMs: number;
8
+ }
9
+ export interface AggregatedReport {
10
+ totalTasks: number;
11
+ tasksByAgent: Map<string, AgentStat>;
12
+ totalToolCalls: number;
13
+ queueWaitStats: {
14
+ avgMs: number;
15
+ p50Ms: number;
16
+ p95Ms: number;
17
+ maxMs: number;
18
+ };
19
+ }
20
+ export declare class PerformanceAggregator {
21
+ private tasks;
22
+ recordTaskCompletion(task: BackgroundTask): void;
23
+ getReport(): AggregatedReport;
24
+ reset(): void;
25
+ get taskCount(): number;
26
+ }
@@ -1,10 +1,25 @@
1
1
  export type BackgroundTaskStatus = "pending" | "running" | "completed" | "error" | "cancelled";
2
+ export interface PhaseTiming {
3
+ /** queuedAt -> startedAt in ms */
4
+ queueWaitMs: number;
5
+ /** startedAt -> completedAt in ms */
6
+ totalRunMs: number;
7
+ /** startedAt -> first assistant message in ms */
8
+ firstResponseMs?: number;
9
+ /** startedAt -> last message in ms */
10
+ lastResponseMs?: number;
11
+ /** Total tool call count */
12
+ toolCallCount: number;
13
+ /** toolCallCount / totalRunMs * 60000 (calls per minute) */
14
+ toolCallRate?: number;
15
+ }
2
16
  export interface TaskProgress {
3
17
  toolCalls: number;
4
18
  lastTool?: string;
5
19
  lastUpdate: Date;
6
20
  lastMessage?: string;
7
21
  lastMessageAt?: Date;
22
+ phaseTiming?: PhaseTiming;
8
23
  }
9
24
  export interface BackgroundTask {
10
25
  id: string;
package/dist/index.js CHANGED
@@ -4781,7 +4781,7 @@ var init_agent_tool_restrictions = __esm(() => {
4781
4781
  });
4782
4782
 
4783
4783
  // src/shared/model-requirements.ts
4784
- var AGENT_MODEL_REQUIREMENTS, CATEGORY_MODEL_REQUIREMENTS;
4784
+ var AGENT_MODEL_REQUIREMENTS;
4785
4785
  var init_model_requirements = __esm(() => {
4786
4786
  AGENT_MODEL_REQUIREMENTS = {
4787
4787
  sisyphus: {
@@ -4850,58 +4850,6 @@ var init_model_requirements = __esm(() => {
4850
4850
  ]
4851
4851
  }
4852
4852
  };
4853
- CATEGORY_MODEL_REQUIREMENTS = {
4854
- "visual-engineering": {
4855
- fallbackChain: [
4856
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" },
4857
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-5", variant: "max" },
4858
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" }
4859
- ]
4860
- },
4861
- ultrabrain: {
4862
- fallbackChain: [
4863
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2-codex", variant: "xhigh" },
4864
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-5", variant: "max" },
4865
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" }
4866
- ]
4867
- },
4868
- artistry: {
4869
- fallbackChain: [
4870
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "max" },
4871
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-5", variant: "max" },
4872
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" }
4873
- ]
4874
- },
4875
- quick: {
4876
- fallbackChain: [
4877
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-haiku-4-5" },
4878
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
4879
- { providers: ["opencode"], model: "gpt-5-nano" }
4880
- ]
4881
- },
4882
- "unspecified-low": {
4883
- fallbackChain: [
4884
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-5" },
4885
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2-codex", variant: "medium" },
4886
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" }
4887
- ]
4888
- },
4889
- "unspecified-high": {
4890
- fallbackChain: [
4891
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-5", variant: "max" },
4892
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
4893
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" }
4894
- ]
4895
- },
4896
- writing: {
4897
- fallbackChain: [
4898
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
4899
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-5" },
4900
- { providers: ["zai-coding-plan"], model: "glm-4.7" },
4901
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" }
4902
- ]
4903
- }
4904
- };
4905
4853
  });
4906
4854
 
4907
4855
  // src/shared/model-availability.ts
@@ -5038,6 +4986,53 @@ var init_model_resolver = __esm(() => {
5038
4986
  init_model_availability();
5039
4987
  });
5040
4988
 
4989
+ // src/shared/perf-timer.ts
4990
+ class PerfTimer {
4991
+ marks = new Map;
4992
+ spans = [];
4993
+ mark(name) {
4994
+ this.marks.set(name, performance.now());
4995
+ }
4996
+ measure(name, startMark, endMark) {
4997
+ const start = this.marks.get(startMark);
4998
+ if (start === undefined)
4999
+ return 0;
5000
+ const end = endMark ? this.marks.get(endMark) ?? performance.now() : performance.now();
5001
+ const duration = end - start;
5002
+ this.spans.push({ name, durationMs: duration, start, end });
5003
+ return duration;
5004
+ }
5005
+ getReport() {
5006
+ return [...this.spans];
5007
+ }
5008
+ reset() {
5009
+ this.marks.clear();
5010
+ this.spans = [];
5011
+ }
5012
+ static formatDuration(start, end, options) {
5013
+ const ms = (end ?? new Date).getTime() - start.getTime();
5014
+ const absMs = Math.abs(ms);
5015
+ const totalSeconds = absMs / 1000;
5016
+ const seconds = Math.floor(totalSeconds);
5017
+ const minutes = Math.floor(seconds / 60);
5018
+ const hours = Math.floor(minutes / 60);
5019
+ const precision = options?.precision ?? "full";
5020
+ if (hours > 0) {
5021
+ if (precision === "compact") {
5022
+ return `${hours}h ${minutes % 60}m`;
5023
+ }
5024
+ return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
5025
+ }
5026
+ if (minutes > 0) {
5027
+ return `${minutes}m ${seconds % 60}s`;
5028
+ }
5029
+ if (seconds === 0 && absMs > 0) {
5030
+ return `${(absMs / 1000).toFixed(1)}s`;
5031
+ }
5032
+ return `${seconds}s`;
5033
+ }
5034
+ }
5035
+
5041
5036
  // src/shared/index.ts
5042
5037
  var init_shared = __esm(() => {
5043
5038
  init_frontmatter();
@@ -19819,7 +19814,9 @@ var KEYWORD_DETECTORS = [
19819
19814
  \u5982\u679C\u590D\u6742\uFF08\u67B6\u6784\u3001\u591A\u7CFB\u7EDF\u30012 \u6B21\u4EE5\u4E0A\u5931\u8D25\u540E\u7684\u8C03\u8BD5\uFF09\uFF1A
19820
19815
  - \u54A8\u8BE2 oracle \u83B7\u53D6\u6218\u7565\u6307\u5BFC
19821
19816
 
19822
- \u7EFC\u5408\u53D1\u73B0\u540E\u518D\u7EE7\u7EED\u3002`
19817
+ \u7EFC\u5408\u53D1\u73B0\u540E\u518D\u7EE7\u7EED\u3002
19818
+ **\u4E2D\u6587\u8BED\u5883\u601D\u8003\u56DE\u590D**
19819
+ `
19823
19820
  }
19824
19821
  ];
19825
19822
 
@@ -41888,19 +41885,6 @@ var BACKGROUND_CANCEL_DESCRIPTION = `\u53D6\u6D88\u6B63\u5728\u8FD0\u884C\u7684\
41888
41885
  // src/tools/background-task/tools.ts
41889
41886
  init_logger();
41890
41887
  init_session_cursor();
41891
- function formatDuration(start, end) {
41892
- const duration3 = (end ?? new Date).getTime() - start.getTime();
41893
- const seconds = Math.floor(duration3 / 1000);
41894
- const minutes = Math.floor(seconds / 60);
41895
- const hours = Math.floor(minutes / 60);
41896
- if (hours > 0) {
41897
- return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
41898
- } else if (minutes > 0) {
41899
- return `${minutes}m ${seconds % 60}s`;
41900
- } else {
41901
- return `${seconds}s`;
41902
- }
41903
- }
41904
41888
  function delay(ms) {
41905
41889
  return new Promise((resolve10) => setTimeout(resolve10, ms));
41906
41890
  }
@@ -41912,9 +41896,9 @@ function truncateText(text, maxLength) {
41912
41896
  function formatTaskStatus(task) {
41913
41897
  let duration3;
41914
41898
  if (task.status === "pending" && task.queuedAt) {
41915
- duration3 = formatDuration(task.queuedAt, undefined);
41899
+ duration3 = PerfTimer.formatDuration(task.queuedAt, undefined);
41916
41900
  } else if (task.startedAt) {
41917
- duration3 = formatDuration(task.startedAt, task.completedAt);
41901
+ duration3 = PerfTimer.formatDuration(task.startedAt, task.completedAt);
41918
41902
  } else {
41919
41903
  duration3 = "N/A";
41920
41904
  }
@@ -41951,6 +41935,18 @@ ${truncated}
41951
41935
  > **Failed**: The task encountered an error. Check the last message for details.`;
41952
41936
  }
41953
41937
  const durationLabel = task.status === "pending" ? "Queued for" : "Duration";
41938
+ let perfBlock = "";
41939
+ if (task.progress?.phaseTiming) {
41940
+ const pt = task.progress.phaseTiming;
41941
+ const queueWait = PerfTimer.formatDuration(new Date(0), new Date(pt.queueWaitMs));
41942
+ perfBlock = `
41943
+
41944
+ ### Performance
41945
+ | Metric | Value |
41946
+ |--------|-------|
41947
+ | Queue wait | ${queueWait} |
41948
+ | Tool calls | ${pt.toolCallCount} |`;
41949
+ }
41954
41950
  return `# Task Status
41955
41951
 
41956
41952
  | Field | Value |
@@ -41966,7 +41962,8 @@ ${statusNote}
41966
41962
 
41967
41963
  \`\`\`
41968
41964
  ${promptPreview}
41969
- \`\`\`${lastMessageSection}`;
41965
+ \`\`\`${lastMessageSection}${perfBlock}
41966
+ }`;
41970
41967
  }
41971
41968
  async function formatTaskResult(task, client2) {
41972
41969
  if (!task.sessionID) {
@@ -41980,12 +41977,25 @@ async function formatTaskResult(task, client2) {
41980
41977
  }
41981
41978
  const messages = messagesResult.data ?? messagesResult;
41982
41979
  if (!Array.isArray(messages) || messages.length === 0) {
41980
+ const duration4 = PerfTimer.formatDuration(task.startedAt ?? new Date, task.completedAt);
41981
+ let perfBlock2 = "";
41982
+ if (task.progress?.phaseTiming) {
41983
+ const pt = task.progress.phaseTiming;
41984
+ const queueWait = PerfTimer.formatDuration(new Date(0), new Date(pt.queueWaitMs));
41985
+ perfBlock2 = `
41986
+
41987
+ ### Performance
41988
+ | Metric | Value |
41989
+ |--------|-------|
41990
+ | Queue wait | ${queueWait} |
41991
+ | Tool calls | ${pt.toolCallCount} |`;
41992
+ }
41983
41993
  return `Task Result
41984
41994
 
41985
41995
  Task ID: ${task.id}
41986
41996
  Description: ${task.description}
41987
- Duration: ${formatDuration(task.startedAt ?? new Date, task.completedAt)}
41988
- Session ID: ${task.sessionID}
41997
+ Duration: ${duration4}
41998
+ Session ID: ${task.sessionID}${perfBlock2}
41989
41999
 
41990
42000
  ---
41991
42001
 
@@ -41993,12 +42003,25 @@ Session ID: ${task.sessionID}
41993
42003
  }
41994
42004
  const relevantMessages = messages.filter((m) => m.info?.role === "assistant" || m.info?.role === "tool");
41995
42005
  if (relevantMessages.length === 0) {
42006
+ const duration4 = PerfTimer.formatDuration(task.startedAt ?? new Date, task.completedAt);
42007
+ let perfBlock2 = "";
42008
+ if (task.progress?.phaseTiming) {
42009
+ const pt = task.progress.phaseTiming;
42010
+ const queueWait = PerfTimer.formatDuration(new Date(0), new Date(pt.queueWaitMs));
42011
+ perfBlock2 = `
42012
+
42013
+ ### Performance
42014
+ | Metric | Value |
42015
+ |--------|-------|
42016
+ | Queue wait | ${queueWait} |
42017
+ | Tool calls | ${pt.toolCallCount} |`;
42018
+ }
41996
42019
  return `Task Result
41997
42020
 
41998
42021
  Task ID: ${task.id}
41999
42022
  Description: ${task.description}
42000
- Duration: ${formatDuration(task.startedAt ?? new Date, task.completedAt)}
42001
- Session ID: ${task.sessionID}
42023
+ Duration: ${duration4}
42024
+ Session ID: ${task.sessionID}${perfBlock2}
42002
42025
 
42003
42026
  ---
42004
42027
 
@@ -42011,13 +42034,25 @@ Session ID: ${task.sessionID}
42011
42034
  });
42012
42035
  const newMessages = consumeNewMessages(task.sessionID, sortedMessages);
42013
42036
  if (newMessages.length === 0) {
42014
- const duration4 = formatDuration(task.startedAt ?? new Date, task.completedAt);
42037
+ const duration4 = PerfTimer.formatDuration(task.startedAt ?? new Date, task.completedAt);
42038
+ let perfBlock2 = "";
42039
+ if (task.progress?.phaseTiming) {
42040
+ const pt = task.progress.phaseTiming;
42041
+ const queueWait = PerfTimer.formatDuration(new Date(0), new Date(pt.queueWaitMs));
42042
+ perfBlock2 = `
42043
+
42044
+ ### Performance
42045
+ | Metric | Value |
42046
+ |--------|-------|
42047
+ | Queue wait | ${queueWait} |
42048
+ | Tool calls | ${pt.toolCallCount} |`;
42049
+ }
42015
42050
  return `Task Result
42016
42051
 
42017
42052
  Task ID: ${task.id}
42018
42053
  Description: ${task.description}
42019
42054
  Duration: ${duration4}
42020
- Session ID: ${task.sessionID}
42055
+ Session ID: ${task.sessionID}${perfBlock2}
42021
42056
 
42022
42057
  ---
42023
42058
 
@@ -42045,13 +42080,25 @@ Session ID: ${task.sessionID}
42045
42080
  const textContent = extractedContent.filter((text) => text.length > 0).join(`
42046
42081
 
42047
42082
  `);
42048
- const duration3 = formatDuration(task.startedAt ?? new Date, task.completedAt);
42083
+ const duration3 = PerfTimer.formatDuration(task.startedAt ?? new Date, task.completedAt);
42084
+ let perfBlock = "";
42085
+ if (task.progress?.phaseTiming) {
42086
+ const pt = task.progress.phaseTiming;
42087
+ const queueWait = PerfTimer.formatDuration(new Date(0), new Date(pt.queueWaitMs));
42088
+ perfBlock = `
42089
+
42090
+ ### Performance
42091
+ | Metric | Value |
42092
+ |--------|-------|
42093
+ | Queue wait | ${queueWait} |
42094
+ | Tool calls | ${pt.toolCallCount} |`;
42095
+ }
42049
42096
  return `Task Result
42050
42097
 
42051
42098
  Task ID: ${task.id}
42052
42099
  Description: ${task.description}
42053
42100
  Duration: ${duration3}
42054
- Session ID: ${task.sessionID}
42101
+ Session ID: ${task.sessionID}${perfBlock}
42055
42102
 
42056
42103
  ---
42057
42104
 
@@ -42687,14 +42734,7 @@ class TaskToastManager {
42687
42734
  return Array.from(this.tasks.values()).filter((t) => t.status === "queued").sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
42688
42735
  }
42689
42736
  formatDuration(startedAt) {
42690
- const seconds = Math.floor((Date.now() - startedAt.getTime()) / 1000);
42691
- if (seconds < 60)
42692
- return `${seconds}s`;
42693
- const minutes = Math.floor(seconds / 60);
42694
- if (minutes < 60)
42695
- return `${minutes}m ${seconds % 60}s`;
42696
- const hours = Math.floor(minutes / 60);
42697
- return `${hours}h ${minutes % 60}m`;
42737
+ return PerfTimer.formatDuration(startedAt, undefined, { precision: "compact" });
42698
42738
  }
42699
42739
  getConcurrencyInfo() {
42700
42740
  if (!this.concurrencyManager)
@@ -42798,9 +42838,6 @@ function initTaskToastManager(client2, concurrencyManager) {
42798
42838
  }
42799
42839
  // src/tools/delegate-task/tools.ts
42800
42840
  init_shared();
42801
- init_model_availability();
42802
- init_model_resolver();
42803
- init_model_requirements();
42804
42841
  var SISYPHUS_JUNIOR_AGENT = "sisyphus-junior";
42805
42842
  function parseModelString(model) {
42806
42843
  const parts = model.split("/");
@@ -42822,17 +42859,6 @@ function getMessageDir9(sessionID) {
42822
42859
  }
42823
42860
  return null;
42824
42861
  }
42825
- function formatDuration2(start, end) {
42826
- const duration3 = (end ?? new Date).getTime() - start.getTime();
42827
- const seconds = Math.floor(duration3 / 1000);
42828
- const minutes = Math.floor(seconds / 60);
42829
- const hours = Math.floor(minutes / 60);
42830
- if (hours > 0)
42831
- return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
42832
- if (minutes > 0)
42833
- return `${minutes}m ${seconds % 60}s`;
42834
- return `${seconds}s`;
42835
- }
42836
42862
  function formatDetailedError(error45, ctx) {
42837
42863
  const message = error45 instanceof Error ? error45.message : String(error45);
42838
42864
  const stack = error45 instanceof Error ? error45.stack : undefined;
@@ -43145,7 +43171,7 @@ Session ID: ${args.session_id}`;
43145
43171
  const textParts = lastMessage?.parts?.filter((p) => p.type === "text" || p.type === "reasoning") ?? [];
43146
43172
  const textContent = textParts.map((p) => p.text ?? "").filter(Boolean).join(`
43147
43173
  `);
43148
- const duration3 = formatDuration2(startTime);
43174
+ const duration3 = PerfTimer.formatDuration(startTime);
43149
43175
  return `\u4EFB\u52A1\u5DF2\u7EE7\u7EED\u5E76\u5728 ${duration3} \u5185\u5B8C\u6210\u3002
43150
43176
 
43151
43177
  Session ID: ${args.session_id}
@@ -43186,195 +43212,21 @@ ${textContent || "(\u65E0\u6587\u672C\u8F93\u51FA)"}
43186
43212
 
43187
43213
  ` + "\uFF08\u66FF\u6362\u4E3A\u4F60\u504F\u597D\u7684 provider/model\uFF09";
43188
43214
  }
43189
- const availableModels = await fetchAvailableModels(client2);
43190
43215
  const resolved = resolveCategoryConfig(args.category, {
43191
43216
  userCategories,
43192
43217
  inheritedModel,
43193
43218
  systemDefaultModel
43194
43219
  });
43195
43220
  if (!resolved) {
43196
- return `\u672A\u77E5\u7684\u5206\u7C7B\uFF1A"${args.category}"\u3002\u53EF\u7528\u7684\u5206\u7C7B\uFF1A${Object.keys({ ...DEFAULT_CATEGORIES, ...userCategories }).join(", ")}`;
43197
- }
43198
- const requirement = CATEGORY_MODEL_REQUIREMENTS[args.category];
43199
- let actualModel;
43200
- if (!requirement) {
43201
- actualModel = resolved.model;
43202
- modelInfo = { model: actualModel, type: "system-default", source: "system-default" };
43203
- } else {
43204
- const { model: resolvedModel, source, variant: resolvedVariant } = resolveModelWithFallback({
43205
- userModel: userCategories?.[args.category]?.model ?? sisyphusJuniorModel,
43206
- fallbackChain: requirement.fallbackChain,
43207
- availableModels,
43208
- systemDefaultModel
43209
- });
43210
- actualModel = resolvedModel;
43211
- if (!parseModelString(actualModel)) {
43212
- return `\u65E0\u6548\u7684\u6A21\u578B\u683C\u5F0F"${actualModel}"\u3002\u671F\u671B"provider/model"\u683C\u5F0F\uFF08\u4F8B\u5982 "anthropic/claude-sonnet-4-5"\uFF09\u3002`;
43213
- }
43214
- let type2;
43215
- switch (source) {
43216
- case "override":
43217
- type2 = "user-defined";
43218
- break;
43219
- case "provider-fallback":
43220
- type2 = "category-default";
43221
- break;
43222
- case "system-default":
43223
- type2 = "system-default";
43224
- break;
43225
- }
43226
- modelInfo = { model: actualModel, type: type2, source };
43227
- const parsedModel = parseModelString(actualModel);
43228
- const variantToUse = userCategories?.[args.category]?.variant ?? resolvedVariant;
43229
- categoryModel = parsedModel ? variantToUse ? { ...parsedModel, variant: variantToUse } : parsedModel : undefined;
43221
+ return `\u672A\u77E5\u7684\u5206\u7C7B\uFF1A${args.category}\u3002\u53EF\u7528\u7684\u5206\u7C7B\uFF1A${categoryExamples}`;
43230
43222
  }
43231
43223
  agentToUse = SISYPHUS_JUNIOR_AGENT;
43232
- if (!categoryModel) {
43233
- const parsedModel = parseModelString(actualModel);
43234
- categoryModel = parsedModel ?? undefined;
43235
- }
43236
- categoryPromptAppend = resolved.promptAppend || undefined;
43237
- const isUnstableAgent = resolved.config.is_unstable_agent === true || actualModel.toLowerCase().includes("gemini");
43238
- const isRunInBackgroundExplicitlyFalse = args.run_in_background === false || args.run_in_background === "false";
43239
- log("[delegate_task] unstable agent detection", {
43240
- category: args.category,
43241
- actualModel,
43242
- isUnstableAgent,
43243
- run_in_background_value: args.run_in_background,
43244
- run_in_background_type: typeof args.run_in_background,
43245
- isRunInBackgroundExplicitlyFalse,
43246
- willForceBackground: isUnstableAgent && isRunInBackgroundExplicitlyFalse
43247
- });
43248
- if (isUnstableAgent && isRunInBackgroundExplicitlyFalse) {
43249
- const systemContent2 = buildSystemContent({ skillContent, categoryPromptAppend });
43250
- try {
43251
- const task = await manager.launch({
43252
- description: args.description,
43253
- prompt: args.prompt,
43254
- agent: agentToUse,
43255
- parentSessionID: ctx.sessionID,
43256
- parentMessageID: ctx.messageID,
43257
- parentModel,
43258
- parentAgent,
43259
- model: categoryModel,
43260
- skills: args.load_skills.length > 0 ? args.load_skills : undefined,
43261
- skillContent: systemContent2
43262
- });
43263
- const WAIT_FOR_SESSION_INTERVAL_MS = 100;
43264
- const WAIT_FOR_SESSION_TIMEOUT_MS = 30000;
43265
- const waitStart = Date.now();
43266
- while (!task.sessionID && Date.now() - waitStart < WAIT_FOR_SESSION_TIMEOUT_MS) {
43267
- if (ctx.abort?.aborted) {
43268
- return `\u7B49\u5F85 session \u542F\u52A8\u65F6\u88AB\u4E2D\u6B62\u3002
43269
-
43270
- Task ID: ${task.id}`;
43271
- }
43272
- await new Promise((resolve10) => setTimeout(resolve10, WAIT_FOR_SESSION_INTERVAL_MS));
43273
- }
43274
- const sessionID = task.sessionID;
43275
- if (!sessionID) {
43276
- return formatDetailedError(new Error(`\u4EFB\u52A1\u5728\u8D85\u65F6\uFF0830\u79D2\uFF09\u5185\u672A\u80FD\u542F\u52A8\u3002Task ID: ${task.id}\uFF0CStatus: ${task.status}`), {
43277
- operation: "\u542F\u52A8\u53D7\u76D1\u63A7\u7684\u540E\u53F0\u4EFB\u52A1",
43278
- args,
43279
- agent: agentToUse,
43280
- category: args.category
43281
- });
43282
- }
43283
- ctx.metadata?.({
43284
- title: args.description,
43285
- metadata: {
43286
- prompt: args.prompt,
43287
- agent: agentToUse,
43288
- category: args.category,
43289
- load_skills: args.load_skills,
43290
- description: args.description,
43291
- run_in_background: args.run_in_background,
43292
- sessionId: sessionID,
43293
- command: args.command
43294
- }
43295
- });
43296
- const startTime = new Date;
43297
- const POLL_INTERVAL_MS = 500;
43298
- const MAX_POLL_TIME_MS = 10 * 60 * 1000;
43299
- const MIN_STABILITY_TIME_MS = 1e4;
43300
- const STABILITY_POLLS_REQUIRED = 3;
43301
- const pollStart = Date.now();
43302
- let lastMsgCount = 0;
43303
- let stablePolls = 0;
43304
- while (Date.now() - pollStart < MAX_POLL_TIME_MS) {
43305
- if (ctx.abort?.aborted) {
43306
- return `\u4EFB\u52A1\u5DF2\u4E2D\u6B62\uFF08\u6B63\u5728\u540E\u53F0\u6A21\u5F0F\u8FD0\u884C\uFF09\u3002
43307
-
43308
- Session ID: ${sessionID}`;
43309
- }
43310
- await new Promise((resolve10) => setTimeout(resolve10, POLL_INTERVAL_MS));
43311
- const statusResult = await client2.session.status();
43312
- const allStatuses = statusResult.data ?? {};
43313
- const sessionStatus = allStatuses[sessionID];
43314
- if (sessionStatus && sessionStatus.type !== "idle") {
43315
- stablePolls = 0;
43316
- lastMsgCount = 0;
43317
- continue;
43318
- }
43319
- if (Date.now() - pollStart < MIN_STABILITY_TIME_MS)
43320
- continue;
43321
- const messagesCheck = await client2.session.messages({ path: { id: sessionID } });
43322
- const msgs = messagesCheck.data ?? messagesCheck;
43323
- const currentMsgCount = msgs.length;
43324
- if (currentMsgCount === lastMsgCount) {
43325
- stablePolls++;
43326
- if (stablePolls >= STABILITY_POLLS_REQUIRED)
43327
- break;
43328
- } else {
43329
- stablePolls = 0;
43330
- lastMsgCount = currentMsgCount;
43331
- }
43332
- }
43333
- const messagesResult = await client2.session.messages({ path: { id: sessionID } });
43334
- const messages = messagesResult.data ?? messagesResult;
43335
- const assistantMessages = messages.filter((m) => m.info?.role === "assistant").sort((a, b) => (b.info?.time?.created ?? 0) - (a.info?.time?.created ?? 0));
43336
- const lastMessage = assistantMessages[0];
43337
- if (!lastMessage) {
43338
- return `\u672A\u627E\u5230 assistant \u7684\u54CD\u5E94\uFF08\u4EFB\u52A1\u5728\u540E\u53F0\u6A21\u5F0F\u8FD0\u884C\uFF09\u3002
43339
-
43340
- Session ID: ${sessionID}`;
43341
- }
43342
- const textParts = lastMessage?.parts?.filter((p) => p.type === "text" || p.type === "reasoning") ?? [];
43343
- const textContent = textParts.map((p) => p.text ?? "").filter(Boolean).join(`
43344
- `);
43345
- const duration3 = formatDuration2(startTime);
43346
- return `\u53D7\u76D1\u63A7\u4EFB\u52A1\u6210\u529F\u5B8C\u6210
43347
-
43348
- \u91CD\u8981\u63D0\u793A\uFF1A\u6B64\u6A21\u578B\uFF08${actualModel}\uFF09\u88AB\u6807\u8BB0\u4E3A\u4E0D\u7A33\u5B9A/\u5B9E\u9A8C\u6027\u3002
43349
- \u4F60\u7684 run_in_background=false \u5DF2\u81EA\u52A8\u8F6C\u6362\u4E3A\u540E\u53F0\u6A21\u5F0F\u4EE5\u8FDB\u884C\u53EF\u9760\u6027\u76D1\u63A7\u3002
43350
-
43351
- \u8017\u65F6\uFF1A${duration3}
43352
- Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}
43353
- Session ID: ${sessionID}
43354
-
43355
- \u76D1\u63A7\u8BF4\u660E\uFF1A
43356
- - \u4EFB\u52A1\u5DF2\u88AB\u76D1\u63A7\u5E76\u6210\u529F\u5B8C\u6210
43357
- - \u5982\u679C\u4F60\u53D1\u73B0\u6B64 agent \u5728\u540E\u7EED\u8C03\u7528\u4E2D\u884C\u4E3A\u5F02\u5E38\uFF0C\u8BF7\u4E3B\u52A8\u76D1\u63A7\u5176\u8FDB\u5EA6
43358
- - \u5982 agent \u4F3C\u4E4E\u5361\u4F4F\u6216\u4EA7\u751F\u5783\u573E\u8F93\u51FA\uFF0C\u4F7F\u7528 background_cancel(task_id="...") \u4E2D\u6B62
43359
- - \u770B\u5230\u6B64\u6D88\u606F\u65F6\u4E0D\u8981\u81EA\u52A8\u91CD\u8BD5 \u2014 \u4EFB\u52A1\u5DF2\u6210\u529F\u5B8C\u6210
43360
-
43361
- ---
43362
-
43363
- \u7ED3\u679C\uFF1A
43364
-
43365
- ${textContent || "(\u65E0\u6587\u672C\u8F93\u51FA)"}
43366
-
43367
- ---
43368
- \u7EE7\u7EED\u6B64 session\uFF1Asession_id="${sessionID}"`;
43369
- } catch (error45) {
43370
- return formatDetailedError(error45, {
43371
- operation: "\u542F\u52A8\u53D7\u76D1\u63A7\u7684\u540E\u53F0\u4EFB\u52A1",
43372
- args,
43373
- agent: agentToUse,
43374
- category: args.category
43375
- });
43376
- }
43377
- }
43224
+ categoryModel = parseModelString(resolved.model);
43225
+ categoryPromptAppend = resolved.promptAppend;
43226
+ modelInfo = {
43227
+ type: "category-default",
43228
+ model: resolved.model
43229
+ };
43378
43230
  } else {
43379
43231
  if (!args.subagent_type?.trim()) {
43380
43232
  return `Agent \u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A\u3002`;
@@ -43612,7 +43464,7 @@ Session ID: ${sessionID}`;
43612
43464
  const textParts = lastMessage?.parts?.filter((p) => p.type === "text" || p.type === "reasoning") ?? [];
43613
43465
  const textContent = textParts.map((p) => p.text ?? "").filter(Boolean).join(`
43614
43466
  `);
43615
- const duration3 = formatDuration2(startTime);
43467
+ const duration3 = PerfTimer.formatDuration(startTime);
43616
43468
  if (toastManager) {
43617
43469
  toastManager.removeTask(taskId);
43618
43470
  }
@@ -43770,6 +43622,65 @@ class ConcurrencyManager {
43770
43622
  }
43771
43623
  }
43772
43624
 
43625
+ // src/features/background-agent/perf-aggregator.ts
43626
+ function percentile(sorted, p) {
43627
+ if (sorted.length === 0)
43628
+ return 0;
43629
+ const index = Math.ceil(p / 100 * sorted.length) - 1;
43630
+ return sorted[Math.max(0, Math.min(index, sorted.length - 1))];
43631
+ }
43632
+
43633
+ class PerformanceAggregator {
43634
+ tasks = [];
43635
+ recordTaskCompletion(task) {
43636
+ if (!task.progress?.phaseTiming)
43637
+ return;
43638
+ this.tasks.push({
43639
+ agent: task.agent,
43640
+ toolCalls: task.progress.phaseTiming.toolCallCount,
43641
+ phaseTiming: task.progress.phaseTiming
43642
+ });
43643
+ }
43644
+ getReport() {
43645
+ const byAgent = new Map;
43646
+ const queueWaits = [];
43647
+ let totalToolCalls = 0;
43648
+ for (const t of this.tasks) {
43649
+ const list = byAgent.get(t.agent) ?? [];
43650
+ list.push(t.phaseTiming.totalRunMs);
43651
+ byAgent.set(t.agent, list);
43652
+ queueWaits.push(t.phaseTiming.queueWaitMs);
43653
+ totalToolCalls += t.toolCalls;
43654
+ }
43655
+ const tasksByAgent = new Map;
43656
+ for (const [agent, durations] of byAgent) {
43657
+ const sorted = [...durations].sort((a, b) => a - b);
43658
+ const totalMs = durations.reduce((s, d) => s + d, 0);
43659
+ tasksByAgent.set(agent, {
43660
+ count: durations.length,
43661
+ avgMs: Math.round(totalMs / durations.length),
43662
+ p50Ms: Math.round(percentile(sorted, 50)),
43663
+ p95Ms: Math.round(percentile(sorted, 95)),
43664
+ totalMs
43665
+ });
43666
+ }
43667
+ const sortedQueue = [...queueWaits].sort((a, b) => a - b);
43668
+ const queueWaitStats = {
43669
+ avgMs: queueWaits.length > 0 ? Math.round(queueWaits.reduce((s, q) => s + q, 0) / queueWaits.length) : 0,
43670
+ p50Ms: Math.round(percentile(sortedQueue, 50)),
43671
+ p95Ms: Math.round(percentile(sortedQueue, 95)),
43672
+ maxMs: queueWaits.length > 0 ? Math.max(...queueWaits) : 0
43673
+ };
43674
+ return { totalTasks: this.tasks.length, tasksByAgent, totalToolCalls, queueWaitStats };
43675
+ }
43676
+ reset() {
43677
+ this.tasks = [];
43678
+ }
43679
+ get taskCount() {
43680
+ return this.tasks.length;
43681
+ }
43682
+ }
43683
+
43773
43684
  // src/features/background-agent/manager.ts
43774
43685
  import { existsSync as existsSync46, readdirSync as readdirSync16 } from "fs";
43775
43686
  import { join as join55 } from "path";
@@ -43791,6 +43702,7 @@ class BackgroundManager {
43791
43702
  concurrencyManager;
43792
43703
  shutdownTriggered = false;
43793
43704
  config;
43705
+ perfAggregator = new PerformanceAggregator;
43794
43706
  queuesByKey = new Map;
43795
43707
  processingKeys = new Set;
43796
43708
  constructor(ctx, config3) {
@@ -44096,6 +44008,9 @@ class BackgroundManager {
44096
44008
  existingTask.parentModel = input.parentModel;
44097
44009
  existingTask.parentAgent = input.parentAgent;
44098
44010
  existingTask.startedAt = new Date;
44011
+ if (existingTask.progress) {
44012
+ existingTask.progress.phaseTiming = undefined;
44013
+ }
44099
44014
  existingTask.progress = {
44100
44015
  toolCalls: existingTask.progress?.toolCalls ?? 0,
44101
44016
  lastUpdate: new Date
@@ -44397,6 +44312,7 @@ class BackgroundManager {
44397
44312
  log("[background-agent] Task already completed, skipping:", { taskId: task.id, status: task.status, source });
44398
44313
  return false;
44399
44314
  }
44315
+ const perfSnapshot = task.progress?.phaseTiming ? { ...task.progress.phaseTiming } : undefined;
44400
44316
  task.status = "completed";
44401
44317
  task.completedAt = new Date;
44402
44318
  if (task.concurrencyKey) {
@@ -44405,15 +44321,16 @@ class BackgroundManager {
44405
44321
  }
44406
44322
  this.markForNotification(task);
44407
44323
  try {
44408
- await this.notifyParentSession(task);
44324
+ await this.notifyParentSession(task, perfSnapshot);
44409
44325
  log(`[background-agent] Task completed via ${source}:`, task.id);
44410
44326
  } catch (err) {
44411
44327
  log("[background-agent] Error in notifyParentSession:", { taskId: task.id, error: err });
44412
44328
  }
44329
+ this.perfAggregator.recordTaskCompletion(task);
44413
44330
  return true;
44414
44331
  }
44415
- async notifyParentSession(task) {
44416
- const duration3 = this.formatDuration(task.startedAt ?? new Date, task.completedAt);
44332
+ async notifyParentSession(task, perfSnapshot) {
44333
+ const duration3 = PerfTimer.formatDuration(task.startedAt ?? new Date, task.completedAt);
44417
44334
  log("[background-agent] notifyParentSession called for task:", task.id);
44418
44335
  const toastManager = getTaskToastManager();
44419
44336
  if (toastManager) {
@@ -44435,6 +44352,16 @@ class BackgroundManager {
44435
44352
  const statusText = task.status === "completed" ? "COMPLETED" : "CANCELLED";
44436
44353
  const errorInfo = task.error ? `
44437
44354
  **Error:** ${task.error}` : "";
44355
+ const fallbackTaskLine = "- `" + task.id + "`: " + task.description;
44356
+ const perfSummary = (() => {
44357
+ if (!perfSnapshot)
44358
+ return "";
44359
+ const completed = Array.from(this.tasks.values()).filter((t) => t.parentSessionID === task.parentSessionID && t.status !== "running" && t.status !== "pending");
44360
+ const ms = completed.reduce((s, t) => s + (t.progress?.phaseTiming?.totalRunMs ?? 0), 0);
44361
+ const avgSec = completed.length > 0 ? Math.round(ms / completed.length / 1000) : 0;
44362
+ const toolTotal = completed.reduce((s, t) => s + (t.progress?.phaseTiming?.toolCallCount ?? 0), 0);
44363
+ return "\u5E73\u5747\u8017\u65F6: " + avgSec + "s | \u603BTool: " + toolTotal;
44364
+ })();
44438
44365
  let notification;
44439
44366
  if (allComplete) {
44440
44367
  const completedTasks = Array.from(this.tasks.values()).filter((t) => t.parentSessionID === task.parentSessionID && t.status !== "running" && t.status !== "pending").map((t) => `- \`${t.id}\`: ${t.description}`).join(`
@@ -44443,7 +44370,8 @@ class BackgroundManager {
44443
44370
  [\u6240\u6709\u540E\u53F0\u4EFB\u52A1\u5DF2\u5B8C\u6210]
44444
44371
 
44445
44372
  **\u5DF2\u5B8C\u6210\uFF1A**
44446
- ${completedTasks || `- \`${task.id}\`: ${task.description}`}
44373
+ ${completedTasks || fallbackTaskLine}
44374
+ ${perfSummary}
44447
44375
 
44448
44376
  \u4F7F\u7528 \`background_output(task_id="<id>")\` \u83B7\u53D6\u6BCF\u4E2A\u4EFB\u52A1\u7684\u7ED3\u679C\u3002
44449
44377
  </system-reminder>`;
@@ -44453,6 +44381,7 @@ ${completedTasks || `- \`${task.id}\`: ${task.description}`}
44453
44381
  **ID:** \`${task.id}\`
44454
44382
  **\u63CF\u8FF0\uFF1A** ${task.description}
44455
44383
  **\u8017\u65F6\uFF1A** ${duration3}${errorInfo}
44384
+ ${perfSnapshot ? `| \u6392\u961F ${PerfTimer.formatDuration(new Date(0), new Date(perfSnapshot.queueWaitMs))} | Tool ${perfSnapshot.toolCallCount} \u6B21` : ""}
44456
44385
 
44457
44386
  **\u8FD8\u6709 ${remainingCount} \u4E2A\u4EFB\u52A1\u6B63\u5728\u8FDB\u884C\u4E2D\u3002** \u6240\u6709\u4EFB\u52A1\u5B8C\u6210\u540E\u4F60\u4F1A\u6536\u5230\u901A\u77E5\u3002
44458
44387
  \u4E0D\u8981\u8F6E\u8BE2\u2014\u2014\u7EE7\u7EED\u6709\u6548\u7387\u7684\u5DE5\u4F5C\u3002
@@ -44511,18 +44440,6 @@ ${completedTasks || `- \`${task.id}\`: ${task.description}`}
44511
44440
  }
44512
44441
  }, 5 * 60 * 1000);
44513
44442
  }
44514
- formatDuration(start, end) {
44515
- const duration3 = (end ?? new Date).getTime() - start.getTime();
44516
- const seconds = Math.floor(duration3 / 1000);
44517
- const minutes = Math.floor(seconds / 60);
44518
- const hours = Math.floor(minutes / 60);
44519
- if (hours > 0) {
44520
- return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
44521
- } else if (minutes > 0) {
44522
- return `${minutes}m ${seconds % 60}s`;
44523
- }
44524
- return `${seconds}s`;
44525
- }
44526
44443
  hasRunningTasks() {
44527
44444
  for (const task of this.tasks.values()) {
44528
44445
  if (task.status === "running")
@@ -44666,6 +44583,17 @@ ${completedTasks || `- \`${task.id}\`: ${task.description}`}
44666
44583
  if (!task.progress) {
44667
44584
  task.progress = { toolCalls: 0, lastUpdate: new Date };
44668
44585
  }
44586
+ if (task.startedAt && !task.progress.phaseTiming) {
44587
+ const now = Date.now();
44588
+ task.progress.phaseTiming = {
44589
+ queueWaitMs: task.queuedAt ? now - task.queuedAt.getTime() : 0,
44590
+ totalRunMs: now - task.startedAt.getTime(),
44591
+ toolCallCount: 0
44592
+ };
44593
+ }
44594
+ if (task.progress.phaseTiming && task.progress.phaseTiming.firstResponseMs === undefined && assistantMsgs.length > 0) {
44595
+ task.progress.phaseTiming.firstResponseMs = Date.now() - task.startedAt.getTime();
44596
+ }
44669
44597
  task.progress.toolCalls = toolCalls;
44670
44598
  task.progress.lastTool = lastTool;
44671
44599
  task.progress.lastUpdate = new Date;
@@ -44718,6 +44646,10 @@ ${completedTasks || `- \`${task.id}\`: ${task.description}`}
44718
44646
  }
44719
44647
  if (!this.hasRunningTasks()) {
44720
44648
  this.stopPolling();
44649
+ if (this.perfAggregator.taskCount >= 2) {
44650
+ const report = this.perfAggregator.getReport();
44651
+ log("[perf] Session report:", report);
44652
+ }
44721
44653
  }
44722
44654
  }
44723
44655
  shutdown() {
@@ -44738,6 +44670,7 @@ ${completedTasks || `- \`${task.id}\`: ${task.description}`}
44738
44670
  this.pendingByParent.clear();
44739
44671
  this.queuesByKey.clear();
44740
44672
  this.processingKeys.clear();
44673
+ this.perfAggregator.reset();
44741
44674
  this.unregisterProcessCleanup();
44742
44675
  log("[background-agent] Shutdown complete");
44743
44676
  }
@@ -28,4 +28,5 @@ export * from "./agent-tool-restrictions";
28
28
  export * from "./model-requirements";
29
29
  export * from "./model-resolver";
30
30
  export * from "./model-availability";
31
+ export * from "./perf-timer";
31
32
  export * from "./case-insensitive";
@@ -0,0 +1,26 @@
1
+ export interface SpanData {
2
+ name: string;
3
+ durationMs: number;
4
+ start: number;
5
+ end: number;
6
+ }
7
+ export interface FormatDurationOptions {
8
+ precision?: "full" | "compact";
9
+ }
10
+ export declare class PerfTimer {
11
+ private marks;
12
+ private spans;
13
+ mark(name: string): void;
14
+ measure(name: string, startMark: string, endMark?: string): number;
15
+ getReport(): SpanData[];
16
+ reset(): void;
17
+ /**
18
+ * Format a duration between two Date objects into a human-readable string.
19
+ *
20
+ * @param start - Start date
21
+ * @param end - End date (defaults to now if undefined)
22
+ * @param options - Formatting options
23
+ * @returns "Xs", "Xm Ys", "Xh Ym Ys" or "Xh Ym" (compact)
24
+ */
25
+ static formatDuration(start: Date, end?: Date, options?: FormatDurationOptions): string;
26
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skj1724/oh-my-opencode",
3
- "version": "3.19.3",
3
+ "version": "3.19.4",
4
4
  "description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",