oh-my-opencode 3.12.0 → 3.12.1
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 +146 -83
- package/dist/config/schema/background-task.d.ts +1 -0
- package/dist/config/schema/hooks.d.ts +1 -0
- package/dist/config/schema/oh-my-opencode-config.d.ts +1 -0
- package/dist/create-hooks.d.ts +1 -0
- package/dist/features/background-agent/constants.d.ts +1 -0
- package/dist/features/background-agent/loop-detector.d.ts +3 -1
- package/dist/features/background-agent/types.d.ts +1 -1
- package/dist/features/builtin-commands/templates/start-work.d.ts +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/todo-description-override/description.d.ts +1 -0
- package/dist/hooks/todo-description-override/hook.d.ts +8 -0
- package/dist/hooks/todo-description-override/index.d.ts +1 -0
- package/dist/index.js +269 -134
- package/dist/oh-my-opencode.schema.json +3 -0
- package/dist/plugin/hooks/create-core-hooks.d.ts +1 -0
- package/dist/plugin/hooks/create-tool-guard-hooks.d.ts +2 -1
- package/dist/shared/connected-providers-cache.d.ts +26 -29
- package/package.json +12 -12
package/dist/cli/index.js
CHANGED
|
@@ -6619,85 +6619,146 @@ var init_agent_tool_restrictions = () => {};
|
|
|
6619
6619
|
// src/shared/connected-providers-cache.ts
|
|
6620
6620
|
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync } from "fs";
|
|
6621
6621
|
import { join as join4 } from "path";
|
|
6622
|
-
function
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
function ensureCacheDir() {
|
|
6626
|
-
const cacheDir = getOmoOpenCodeCacheDir();
|
|
6627
|
-
if (!existsSync3(cacheDir)) {
|
|
6628
|
-
mkdirSync(cacheDir, { recursive: true });
|
|
6622
|
+
function createConnectedProvidersCacheStore(getCacheDir2 = getOmoOpenCodeCacheDir) {
|
|
6623
|
+
function getCacheFilePath(filename) {
|
|
6624
|
+
return join4(getCacheDir2(), filename);
|
|
6629
6625
|
}
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
connected,
|
|
6636
|
-
updatedAt: new Date().toISOString()
|
|
6637
|
-
};
|
|
6638
|
-
try {
|
|
6639
|
-
writeFileSync2(cacheFile, JSON.stringify(data, null, 2));
|
|
6640
|
-
log("[connected-providers-cache] Cache written", { count: connected.length });
|
|
6641
|
-
} catch (err) {
|
|
6642
|
-
log("[connected-providers-cache] Error writing cache", { error: String(err) });
|
|
6626
|
+
function ensureCacheDir() {
|
|
6627
|
+
const cacheDir = getCacheDir2();
|
|
6628
|
+
if (!existsSync3(cacheDir)) {
|
|
6629
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
6630
|
+
}
|
|
6643
6631
|
}
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
providerCount: Object.keys(data.models).length
|
|
6660
|
-
});
|
|
6661
|
-
} catch (err) {
|
|
6662
|
-
log("[connected-providers-cache] Error writing provider-models cache", { error: String(err) });
|
|
6632
|
+
function readConnectedProvidersCache() {
|
|
6633
|
+
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
|
|
6634
|
+
if (!existsSync3(cacheFile)) {
|
|
6635
|
+
log("[connected-providers-cache] Cache file not found", { cacheFile });
|
|
6636
|
+
return null;
|
|
6637
|
+
}
|
|
6638
|
+
try {
|
|
6639
|
+
const content = readFileSync2(cacheFile, "utf-8");
|
|
6640
|
+
const data = JSON.parse(content);
|
|
6641
|
+
log("[connected-providers-cache] Read cache", { count: data.connected.length, updatedAt: data.updatedAt });
|
|
6642
|
+
return data.connected;
|
|
6643
|
+
} catch (err) {
|
|
6644
|
+
log("[connected-providers-cache] Error reading cache", { error: String(err) });
|
|
6645
|
+
return null;
|
|
6646
|
+
}
|
|
6663
6647
|
}
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
log("[connected-providers-cache] client.provider.list not available");
|
|
6668
|
-
return;
|
|
6648
|
+
function hasConnectedProvidersCache() {
|
|
6649
|
+
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
|
|
6650
|
+
return existsSync3(cacheFile);
|
|
6669
6651
|
}
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
const
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
}
|
|
6684
|
-
}
|
|
6685
|
-
log("[connected-providers-cache] Extracted models from provider list", {
|
|
6686
|
-
providerCount: Object.keys(modelsByProvider).length,
|
|
6687
|
-
totalModels: Object.values(modelsByProvider).reduce((sum, ids) => sum + ids.length, 0)
|
|
6688
|
-
});
|
|
6689
|
-
writeProviderModelsCache({
|
|
6690
|
-
models: modelsByProvider,
|
|
6691
|
-
connected
|
|
6692
|
-
});
|
|
6693
|
-
} catch (err) {
|
|
6694
|
-
log("[connected-providers-cache] Error updating cache", { error: String(err) });
|
|
6652
|
+
function writeConnectedProvidersCache(connected) {
|
|
6653
|
+
ensureCacheDir();
|
|
6654
|
+
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
|
|
6655
|
+
const data = {
|
|
6656
|
+
connected,
|
|
6657
|
+
updatedAt: new Date().toISOString()
|
|
6658
|
+
};
|
|
6659
|
+
try {
|
|
6660
|
+
writeFileSync2(cacheFile, JSON.stringify(data, null, 2));
|
|
6661
|
+
log("[connected-providers-cache] Cache written", { count: connected.length });
|
|
6662
|
+
} catch (err) {
|
|
6663
|
+
log("[connected-providers-cache] Error writing cache", { error: String(err) });
|
|
6664
|
+
}
|
|
6695
6665
|
}
|
|
6666
|
+
function readProviderModelsCache() {
|
|
6667
|
+
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
|
|
6668
|
+
if (!existsSync3(cacheFile)) {
|
|
6669
|
+
log("[connected-providers-cache] Provider-models cache file not found", { cacheFile });
|
|
6670
|
+
return null;
|
|
6671
|
+
}
|
|
6672
|
+
try {
|
|
6673
|
+
const content = readFileSync2(cacheFile, "utf-8");
|
|
6674
|
+
const data = JSON.parse(content);
|
|
6675
|
+
log("[connected-providers-cache] Read provider-models cache", {
|
|
6676
|
+
providerCount: Object.keys(data.models).length,
|
|
6677
|
+
updatedAt: data.updatedAt
|
|
6678
|
+
});
|
|
6679
|
+
return data;
|
|
6680
|
+
} catch (err) {
|
|
6681
|
+
log("[connected-providers-cache] Error reading provider-models cache", { error: String(err) });
|
|
6682
|
+
return null;
|
|
6683
|
+
}
|
|
6684
|
+
}
|
|
6685
|
+
function hasProviderModelsCache() {
|
|
6686
|
+
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
|
|
6687
|
+
return existsSync3(cacheFile);
|
|
6688
|
+
}
|
|
6689
|
+
function writeProviderModelsCache(data) {
|
|
6690
|
+
ensureCacheDir();
|
|
6691
|
+
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
|
|
6692
|
+
const cacheData = {
|
|
6693
|
+
...data,
|
|
6694
|
+
updatedAt: new Date().toISOString()
|
|
6695
|
+
};
|
|
6696
|
+
try {
|
|
6697
|
+
writeFileSync2(cacheFile, JSON.stringify(cacheData, null, 2));
|
|
6698
|
+
log("[connected-providers-cache] Provider-models cache written", {
|
|
6699
|
+
providerCount: Object.keys(data.models).length
|
|
6700
|
+
});
|
|
6701
|
+
} catch (err) {
|
|
6702
|
+
log("[connected-providers-cache] Error writing provider-models cache", { error: String(err) });
|
|
6703
|
+
}
|
|
6704
|
+
}
|
|
6705
|
+
async function updateConnectedProvidersCache(client) {
|
|
6706
|
+
if (!client?.provider?.list) {
|
|
6707
|
+
log("[connected-providers-cache] client.provider.list not available");
|
|
6708
|
+
return;
|
|
6709
|
+
}
|
|
6710
|
+
try {
|
|
6711
|
+
const result = await client.provider.list();
|
|
6712
|
+
const connected = result.data?.connected ?? [];
|
|
6713
|
+
log("[connected-providers-cache] Fetched connected providers", {
|
|
6714
|
+
count: connected.length,
|
|
6715
|
+
providers: connected
|
|
6716
|
+
});
|
|
6717
|
+
writeConnectedProvidersCache(connected);
|
|
6718
|
+
const modelsByProvider = {};
|
|
6719
|
+
const allProviders = result.data?.all ?? [];
|
|
6720
|
+
for (const provider of allProviders) {
|
|
6721
|
+
if (provider.models) {
|
|
6722
|
+
const modelIds = Object.keys(provider.models);
|
|
6723
|
+
if (modelIds.length > 0) {
|
|
6724
|
+
modelsByProvider[provider.id] = modelIds;
|
|
6725
|
+
}
|
|
6726
|
+
}
|
|
6727
|
+
}
|
|
6728
|
+
log("[connected-providers-cache] Extracted models from provider list", {
|
|
6729
|
+
providerCount: Object.keys(modelsByProvider).length,
|
|
6730
|
+
totalModels: Object.values(modelsByProvider).reduce((sum, ids) => sum + ids.length, 0)
|
|
6731
|
+
});
|
|
6732
|
+
writeProviderModelsCache({
|
|
6733
|
+
models: modelsByProvider,
|
|
6734
|
+
connected
|
|
6735
|
+
});
|
|
6736
|
+
} catch (err) {
|
|
6737
|
+
log("[connected-providers-cache] Error updating cache", { error: String(err) });
|
|
6738
|
+
}
|
|
6739
|
+
}
|
|
6740
|
+
return {
|
|
6741
|
+
readConnectedProvidersCache,
|
|
6742
|
+
hasConnectedProvidersCache,
|
|
6743
|
+
readProviderModelsCache,
|
|
6744
|
+
hasProviderModelsCache,
|
|
6745
|
+
writeProviderModelsCache,
|
|
6746
|
+
updateConnectedProvidersCache
|
|
6747
|
+
};
|
|
6696
6748
|
}
|
|
6697
|
-
var CONNECTED_PROVIDERS_CACHE_FILE = "connected-providers.json", PROVIDER_MODELS_CACHE_FILE = "provider-models.json";
|
|
6749
|
+
var CONNECTED_PROVIDERS_CACHE_FILE = "connected-providers.json", PROVIDER_MODELS_CACHE_FILE = "provider-models.json", defaultConnectedProvidersCacheStore, readConnectedProvidersCache, hasConnectedProvidersCache, readProviderModelsCache, hasProviderModelsCache, writeProviderModelsCache, updateConnectedProvidersCache;
|
|
6698
6750
|
var init_connected_providers_cache = __esm(() => {
|
|
6699
6751
|
init_logger();
|
|
6700
6752
|
init_data_path();
|
|
6753
|
+
defaultConnectedProvidersCacheStore = createConnectedProvidersCacheStore(() => getOmoOpenCodeCacheDir());
|
|
6754
|
+
({
|
|
6755
|
+
readConnectedProvidersCache,
|
|
6756
|
+
hasConnectedProvidersCache,
|
|
6757
|
+
readProviderModelsCache,
|
|
6758
|
+
hasProviderModelsCache,
|
|
6759
|
+
writeProviderModelsCache,
|
|
6760
|
+
updateConnectedProvidersCache
|
|
6761
|
+
} = defaultConnectedProvidersCacheStore);
|
|
6701
6762
|
});
|
|
6702
6763
|
|
|
6703
6764
|
// src/shared/model-availability.ts
|
|
@@ -8836,7 +8897,7 @@ var {
|
|
|
8836
8897
|
// package.json
|
|
8837
8898
|
var package_default = {
|
|
8838
8899
|
name: "oh-my-opencode",
|
|
8839
|
-
version: "3.12.
|
|
8900
|
+
version: "3.12.1",
|
|
8840
8901
|
description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
|
|
8841
8902
|
main: "dist/index.js",
|
|
8842
8903
|
types: "dist/index.d.ts",
|
|
@@ -8912,17 +8973,17 @@ var package_default = {
|
|
|
8912
8973
|
typescript: "^5.7.3"
|
|
8913
8974
|
},
|
|
8914
8975
|
optionalDependencies: {
|
|
8915
|
-
"oh-my-opencode-darwin-arm64": "3.12.
|
|
8916
|
-
"oh-my-opencode-darwin-x64": "3.12.
|
|
8917
|
-
"oh-my-opencode-darwin-x64-baseline": "3.12.
|
|
8918
|
-
"oh-my-opencode-linux-arm64": "3.12.
|
|
8919
|
-
"oh-my-opencode-linux-arm64-musl": "3.12.
|
|
8920
|
-
"oh-my-opencode-linux-x64": "3.12.
|
|
8921
|
-
"oh-my-opencode-linux-x64-baseline": "3.12.
|
|
8922
|
-
"oh-my-opencode-linux-x64-musl": "3.12.
|
|
8923
|
-
"oh-my-opencode-linux-x64-musl-baseline": "3.12.
|
|
8924
|
-
"oh-my-opencode-windows-x64": "3.12.
|
|
8925
|
-
"oh-my-opencode-windows-x64-baseline": "3.12.
|
|
8976
|
+
"oh-my-opencode-darwin-arm64": "3.12.1",
|
|
8977
|
+
"oh-my-opencode-darwin-x64": "3.12.1",
|
|
8978
|
+
"oh-my-opencode-darwin-x64-baseline": "3.12.1",
|
|
8979
|
+
"oh-my-opencode-linux-arm64": "3.12.1",
|
|
8980
|
+
"oh-my-opencode-linux-arm64-musl": "3.12.1",
|
|
8981
|
+
"oh-my-opencode-linux-x64": "3.12.1",
|
|
8982
|
+
"oh-my-opencode-linux-x64-baseline": "3.12.1",
|
|
8983
|
+
"oh-my-opencode-linux-x64-musl": "3.12.1",
|
|
8984
|
+
"oh-my-opencode-linux-x64-musl-baseline": "3.12.1",
|
|
8985
|
+
"oh-my-opencode-windows-x64": "3.12.1",
|
|
8986
|
+
"oh-my-opencode-windows-x64-baseline": "3.12.1"
|
|
8926
8987
|
},
|
|
8927
8988
|
overrides: {
|
|
8928
8989
|
"@opencode-ai/sdk": "^1.2.24"
|
|
@@ -24301,6 +24362,7 @@ var BabysittingConfigSchema = exports_external.object({
|
|
|
24301
24362
|
});
|
|
24302
24363
|
// src/config/schema/background-task.ts
|
|
24303
24364
|
var CircuitBreakerConfigSchema = exports_external.object({
|
|
24365
|
+
enabled: exports_external.boolean().optional(),
|
|
24304
24366
|
maxToolCalls: exports_external.number().int().min(10).optional(),
|
|
24305
24367
|
windowSize: exports_external.number().int().min(5).optional(),
|
|
24306
24368
|
repetitionThresholdPercent: exports_external.number().gt(0).max(100).optional()
|
|
@@ -24493,7 +24555,8 @@ var HookNameSchema = exports_external.enum([
|
|
|
24493
24555
|
"write-existing-file-guard",
|
|
24494
24556
|
"anthropic-effort",
|
|
24495
24557
|
"hashline-read-enhancer",
|
|
24496
|
-
"read-image-resizer"
|
|
24558
|
+
"read-image-resizer",
|
|
24559
|
+
"todo-description-override"
|
|
24497
24560
|
]);
|
|
24498
24561
|
// src/config/schema/notification.ts
|
|
24499
24562
|
var NotificationConfigSchema = exports_external.object({
|
|
@@ -10,6 +10,7 @@ export declare const BackgroundTaskConfigSchema: z.ZodObject<{
|
|
|
10
10
|
syncPollTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
11
11
|
maxToolCalls: z.ZodOptional<z.ZodNumber>;
|
|
12
12
|
circuitBreaker: z.ZodOptional<z.ZodObject<{
|
|
13
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
13
14
|
maxToolCalls: z.ZodOptional<z.ZodNumber>;
|
|
14
15
|
windowSize: z.ZodOptional<z.ZodNumber>;
|
|
15
16
|
repetitionThresholdPercent: z.ZodOptional<z.ZodNumber>;
|
|
@@ -48,5 +48,6 @@ export declare const HookNameSchema: z.ZodEnum<{
|
|
|
48
48
|
"anthropic-effort": "anthropic-effort";
|
|
49
49
|
"hashline-read-enhancer": "hashline-read-enhancer";
|
|
50
50
|
"read-image-resizer": "read-image-resizer";
|
|
51
|
+
"todo-description-override": "todo-description-override";
|
|
51
52
|
}>;
|
|
52
53
|
export type HookName = z.infer<typeof HookNameSchema>;
|
|
@@ -1343,6 +1343,7 @@ export declare const OhMyOpenCodeConfigSchema: z.ZodObject<{
|
|
|
1343
1343
|
syncPollTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
1344
1344
|
maxToolCalls: z.ZodOptional<z.ZodNumber>;
|
|
1345
1345
|
circuitBreaker: z.ZodOptional<z.ZodObject<{
|
|
1346
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
1346
1347
|
maxToolCalls: z.ZodOptional<z.ZodNumber>;
|
|
1347
1348
|
windowSize: z.ZodOptional<z.ZodNumber>;
|
|
1348
1349
|
repetitionThresholdPercent: z.ZodOptional<z.ZodNumber>;
|
package/dist/create-hooks.d.ts
CHANGED
|
@@ -50,6 +50,7 @@ export declare function createHooks(args: {
|
|
|
50
50
|
hashlineReadEnhancer: ReturnType<typeof import("./hooks").createHashlineReadEnhancerHook> | null;
|
|
51
51
|
jsonErrorRecovery: ReturnType<typeof import("./hooks").createJsonErrorRecoveryHook> | null;
|
|
52
52
|
readImageResizer: ReturnType<typeof import("./hooks").createReadImageResizerHook> | null;
|
|
53
|
+
todoDescriptionOverride: ReturnType<typeof import("./hooks").createTodoDescriptionOverrideHook> | null;
|
|
53
54
|
contextWindowMonitor: ReturnType<typeof import("./hooks").createContextWindowMonitorHook> | null;
|
|
54
55
|
preemptiveCompaction: ReturnType<typeof import("./hooks").createPreemptiveCompactionHook> | null;
|
|
55
56
|
sessionRecovery: ReturnType<typeof import("./hooks").createSessionRecoveryHook> | null;
|
|
@@ -8,6 +8,7 @@ export declare const DEFAULT_MESSAGE_STALENESS_TIMEOUT_MS = 1800000;
|
|
|
8
8
|
export declare const DEFAULT_MAX_TOOL_CALLS = 200;
|
|
9
9
|
export declare const DEFAULT_CIRCUIT_BREAKER_WINDOW_SIZE = 20;
|
|
10
10
|
export declare const DEFAULT_CIRCUIT_BREAKER_REPETITION_THRESHOLD_PERCENT = 80;
|
|
11
|
+
export declare const DEFAULT_CIRCUIT_BREAKER_ENABLED = true;
|
|
11
12
|
export declare const MIN_RUNTIME_BEFORE_STALE_MS = 30000;
|
|
12
13
|
export declare const MIN_IDLE_TIME_MS = 5000;
|
|
13
14
|
export declare const POLLING_INTERVAL_MS = 3000;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { BackgroundTaskConfig } from "../../config/schema";
|
|
2
2
|
import type { ToolCallWindow } from "./types";
|
|
3
3
|
export interface CircuitBreakerSettings {
|
|
4
|
+
enabled: boolean;
|
|
4
5
|
maxToolCalls: number;
|
|
5
6
|
windowSize: number;
|
|
6
7
|
repetitionThresholdPercent: number;
|
|
@@ -13,5 +14,6 @@ export interface ToolLoopDetectionResult {
|
|
|
13
14
|
thresholdPercent?: number;
|
|
14
15
|
}
|
|
15
16
|
export declare function resolveCircuitBreakerSettings(config?: BackgroundTaskConfig): CircuitBreakerSettings;
|
|
16
|
-
export declare function recordToolCall(window: ToolCallWindow | undefined, toolName: string, settings: CircuitBreakerSettings): ToolCallWindow;
|
|
17
|
+
export declare function recordToolCall(window: ToolCallWindow | undefined, toolName: string, settings: CircuitBreakerSettings, toolInput?: Record<string, unknown> | null): ToolCallWindow;
|
|
18
|
+
export declare function createToolCallSignature(toolName: string, toolInput?: Record<string, unknown> | null): string;
|
|
17
19
|
export declare function detectRepetitiveToolUse(window: ToolCallWindow | undefined): ToolLoopDetectionResult;
|
|
@@ -2,7 +2,7 @@ import type { FallbackEntry } from "../../shared/model-requirements";
|
|
|
2
2
|
import type { SessionPermissionRule } from "../../shared/question-denied-session-permission";
|
|
3
3
|
export type BackgroundTaskStatus = "pending" | "running" | "completed" | "error" | "cancelled" | "interrupt";
|
|
4
4
|
export interface ToolCallWindow {
|
|
5
|
-
|
|
5
|
+
toolSignatures: string[];
|
|
6
6
|
windowSize: number;
|
|
7
7
|
thresholdPercent: number;
|
|
8
8
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const START_WORK_TEMPLATE = "You are starting a Sisyphus work session.\n\n## ARGUMENTS\n\n- `/start-work [plan-name] [--worktree <path>]`\n - `plan-name` (optional): name or partial match of the plan to start\n - `--worktree <path>` (optional): absolute path to an existing git worktree to work in\n - If specified and valid: hook pre-sets worktree_path in boulder.json\n - If specified but invalid: you must run `git worktree add <path> <branch>` first\n - If omitted:
|
|
1
|
+
export declare const START_WORK_TEMPLATE = "You are starting a Sisyphus work session.\n\n## ARGUMENTS\n\n- `/start-work [plan-name] [--worktree <path>]`\n - `plan-name` (optional): name or partial match of the plan to start\n - `--worktree <path>` (optional): absolute path to an existing git worktree to work in\n - If specified and valid: hook pre-sets worktree_path in boulder.json\n - If specified but invalid: you must run `git worktree add <path> <branch>` first\n - If omitted: work directly in the current project directory (no worktree)\n\n## WHAT TO DO\n\n1. **Find available plans**: Search for Prometheus-generated plan files at `.sisyphus/plans/`\n\n2. **Check for active boulder state**: Read `.sisyphus/boulder.json` if it exists\n\n3. **Decision logic**:\n - If `.sisyphus/boulder.json` exists AND plan is NOT complete (has unchecked boxes):\n - **APPEND** current session to session_ids\n - Continue work on existing plan\n - If no active plan OR plan is complete:\n - List available plan files\n - If ONE plan: auto-select it\n - If MULTIPLE plans: show list with timestamps, ask user to select\n\n4. **Worktree Setup** (ONLY when `--worktree` was explicitly specified and `worktree_path` not already set in boulder.json):\n 1. `git worktree list --porcelain` \u2014 see available worktrees\n 2. Create: `git worktree add <absolute-path> <branch-or-HEAD>`\n 3. Update boulder.json to add `\"worktree_path\": \"<absolute-path>\"`\n 4. All work happens inside that worktree directory\n\n5. **Create/Update boulder.json**:\n ```json\n {\n \"active_plan\": \"/absolute/path/to/plan.md\",\n \"started_at\": \"ISO_TIMESTAMP\",\n \"session_ids\": [\"session_id_1\", \"session_id_2\"],\n \"plan_name\": \"plan-name\",\n \"worktree_path\": \"/absolute/path/to/git/worktree\"\n }\n ```\n\n6. **Read the plan file** and start executing tasks according to atlas workflow\n\n## OUTPUT FORMAT\n\nWhen listing plans for selection:\n```\nAvailable Work Plans\n\nCurrent Time: {ISO timestamp}\nSession ID: {current session id}\n\n1. [plan-name-1.md] - Modified: {date} - Progress: 3/10 tasks\n2. [plan-name-2.md] - Modified: {date} - Progress: 0/5 tasks\n\nWhich plan would you like to work on? (Enter number or plan name)\n```\n\nWhen resuming existing work:\n```\nResuming Work Session\n\nActive Plan: {plan-name}\nProgress: {completed}/{total} tasks\nSessions: {count} (appending current session)\nWorktree: {worktree_path}\n\nReading plan and continuing from last incomplete task...\n```\n\nWhen auto-selecting single plan:\n```\nStarting Work Session\n\nPlan: {plan-name}\nSession ID: {session_id}\nStarted: {timestamp}\nWorktree: {worktree_path}\n\nReading plan and beginning execution...\n```\n\n## CRITICAL\n\n- The session_id is injected by the hook - use it directly\n- Always update boulder.json BEFORE starting work\n- If worktree_path is set in boulder.json, all work happens inside that worktree directory\n- Read the FULL plan file before delegating any tasks\n- Follow atlas delegation protocols (7-section format)\n\n## TASK BREAKDOWN (MANDATORY)\n\nAfter reading the plan file, you MUST decompose every plan task into granular, implementation-level sub-steps and register ALL of them as task/todo items BEFORE starting any work.\n\n**How to break down**:\n- Each plan checkbox item (e.g., `- [ ] Add user authentication`) must be split into concrete, actionable sub-tasks\n- Sub-tasks should be specific enough that each one touches a clear set of files/functions\n- Include: file to modify, what to change, expected behavior, and how to verify\n- Do NOT leave any task vague \u2014 \"implement feature X\" is NOT acceptable; \"add validateToken() to src/auth/middleware.ts that checks JWT expiry and returns 401\" IS acceptable\n\n**Example breakdown**:\nPlan task: `- [ ] Add rate limiting to API`\n\u2192 Todo items:\n 1. Create `src/middleware/rate-limiter.ts` with sliding window algorithm (max 100 req/min per IP)\n 2. Add RateLimiter middleware to `src/app.ts` router chain, before auth middleware\n 3. Add rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining) to response in `rate-limiter.ts`\n 4. Add test: verify 429 response after exceeding limit in `src/middleware/rate-limiter.test.ts`\n 5. Add test: verify headers are present on normal responses\n\nRegister these as task/todo items so progress is tracked and visible throughout the session.\n\n## WORKTREE COMPLETION\n\nWhen working in a worktree (`worktree_path` is set in boulder.json) and ALL plan tasks are complete:\n1. Commit all remaining changes in the worktree\n2. Switch to the main working directory (the original repo, NOT the worktree)\n3. Merge the worktree branch into the current branch: `git merge <worktree-branch>`\n4. If merge succeeds, clean up: `git worktree remove <worktree-path>`\n5. Remove the boulder.json state\n\nThis is the DEFAULT behavior when `--worktree` was used. Skip merge only if the user explicitly instructs otherwise (e.g., asks to create a PR instead).";
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -48,3 +48,4 @@ export { createWriteExistingFileGuardHook } from "./write-existing-file-guard";
|
|
|
48
48
|
export { createHashlineReadEnhancerHook } from "./hashline-read-enhancer";
|
|
49
49
|
export { createJsonErrorRecoveryHook, JSON_ERROR_TOOL_EXCLUDE_LIST, JSON_ERROR_PATTERNS, JSON_ERROR_REMINDER } from "./json-error-recovery";
|
|
50
50
|
export { createReadImageResizerHook } from "./read-image-resizer";
|
|
51
|
+
export { createTodoDescriptionOverrideHook } from "./todo-description-override";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const TODOWRITE_DESCRIPTION = "Use this tool to create and manage a structured task list for tracking progress on multi-step work.\n\n## Todo Format (MANDATORY)\n\nEach todo title MUST encode four elements: WHERE, WHY, HOW, and EXPECTED RESULT.\n\nFormat: \"[WHERE] [HOW] to [WHY] \u2014 expect [RESULT]\"\n\nGOOD:\n- \"src/utils/validation.ts: Add validateEmail() for input sanitization \u2014 returns boolean\"\n- \"UserService.create(): Call validateEmail() before DB insert \u2014 rejects invalid emails with 400\"\n- \"validation.test.ts: Add test for missing @ sign \u2014 expect validateEmail('foo') to return false\"\n\nBAD:\n- \"Implement email validation\" (where? how? what result?)\n- \"Add dark mode\" (this is a feature, not a todo)\n- \"Fix auth\" (what file? what changes? what's expected?)\n\n## Granularity Rules\n\nEach todo MUST be a single atomic action completable in 1-3 tool calls. If it needs more, split it.\n\n**Size test**: Can you complete this todo by editing one file or running one command? If not, it's too big.\n\n## Task Management\n- One in_progress at a time. Complete it before starting the next.\n- Mark completed immediately after finishing each item.\n- Skip this tool for single trivial tasks (one-step, obvious action).";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createTodoDescriptionOverrideHook } from "./hook";
|
package/dist/index.js
CHANGED
|
@@ -17584,120 +17584,142 @@ import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync
|
|
|
17584
17584
|
import { join as join9 } from "path";
|
|
17585
17585
|
var CONNECTED_PROVIDERS_CACHE_FILE = "connected-providers.json";
|
|
17586
17586
|
var PROVIDER_MODELS_CACHE_FILE = "provider-models.json";
|
|
17587
|
-
function
|
|
17588
|
-
|
|
17589
|
-
|
|
17590
|
-
function ensureCacheDir2() {
|
|
17591
|
-
const cacheDir = getOmoOpenCodeCacheDir();
|
|
17592
|
-
if (!existsSync8(cacheDir)) {
|
|
17593
|
-
mkdirSync2(cacheDir, { recursive: true });
|
|
17587
|
+
function createConnectedProvidersCacheStore(getCacheDir2 = getOmoOpenCodeCacheDir) {
|
|
17588
|
+
function getCacheFilePath(filename) {
|
|
17589
|
+
return join9(getCacheDir2(), filename);
|
|
17594
17590
|
}
|
|
17595
|
-
|
|
17596
|
-
|
|
17597
|
-
|
|
17598
|
-
|
|
17599
|
-
|
|
17600
|
-
return null;
|
|
17591
|
+
function ensureCacheDir2() {
|
|
17592
|
+
const cacheDir = getCacheDir2();
|
|
17593
|
+
if (!existsSync8(cacheDir)) {
|
|
17594
|
+
mkdirSync2(cacheDir, { recursive: true });
|
|
17595
|
+
}
|
|
17601
17596
|
}
|
|
17602
|
-
|
|
17603
|
-
const
|
|
17604
|
-
|
|
17605
|
-
|
|
17606
|
-
|
|
17607
|
-
|
|
17608
|
-
|
|
17609
|
-
|
|
17597
|
+
function readConnectedProvidersCache() {
|
|
17598
|
+
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
|
|
17599
|
+
if (!existsSync8(cacheFile)) {
|
|
17600
|
+
log("[connected-providers-cache] Cache file not found", { cacheFile });
|
|
17601
|
+
return null;
|
|
17602
|
+
}
|
|
17603
|
+
try {
|
|
17604
|
+
const content = readFileSync4(cacheFile, "utf-8");
|
|
17605
|
+
const data = JSON.parse(content);
|
|
17606
|
+
log("[connected-providers-cache] Read cache", { count: data.connected.length, updatedAt: data.updatedAt });
|
|
17607
|
+
return data.connected;
|
|
17608
|
+
} catch (err) {
|
|
17609
|
+
log("[connected-providers-cache] Error reading cache", { error: String(err) });
|
|
17610
|
+
return null;
|
|
17611
|
+
}
|
|
17610
17612
|
}
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
17614
|
-
return existsSync8(cacheFile);
|
|
17615
|
-
}
|
|
17616
|
-
function writeConnectedProvidersCache(connected) {
|
|
17617
|
-
ensureCacheDir2();
|
|
17618
|
-
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
|
|
17619
|
-
const data = {
|
|
17620
|
-
connected,
|
|
17621
|
-
updatedAt: new Date().toISOString()
|
|
17622
|
-
};
|
|
17623
|
-
try {
|
|
17624
|
-
writeFileSync2(cacheFile, JSON.stringify(data, null, 2));
|
|
17625
|
-
log("[connected-providers-cache] Cache written", { count: connected.length });
|
|
17626
|
-
} catch (err) {
|
|
17627
|
-
log("[connected-providers-cache] Error writing cache", { error: String(err) });
|
|
17613
|
+
function hasConnectedProvidersCache() {
|
|
17614
|
+
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
|
|
17615
|
+
return existsSync8(cacheFile);
|
|
17628
17616
|
}
|
|
17629
|
-
|
|
17630
|
-
|
|
17631
|
-
|
|
17632
|
-
|
|
17633
|
-
|
|
17634
|
-
|
|
17617
|
+
function writeConnectedProvidersCache(connected) {
|
|
17618
|
+
ensureCacheDir2();
|
|
17619
|
+
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
|
|
17620
|
+
const data = {
|
|
17621
|
+
connected,
|
|
17622
|
+
updatedAt: new Date().toISOString()
|
|
17623
|
+
};
|
|
17624
|
+
try {
|
|
17625
|
+
writeFileSync2(cacheFile, JSON.stringify(data, null, 2));
|
|
17626
|
+
log("[connected-providers-cache] Cache written", { count: connected.length });
|
|
17627
|
+
} catch (err) {
|
|
17628
|
+
log("[connected-providers-cache] Error writing cache", { error: String(err) });
|
|
17629
|
+
}
|
|
17635
17630
|
}
|
|
17636
|
-
|
|
17637
|
-
const
|
|
17638
|
-
|
|
17639
|
-
|
|
17640
|
-
|
|
17641
|
-
|
|
17642
|
-
|
|
17643
|
-
|
|
17644
|
-
|
|
17645
|
-
|
|
17646
|
-
|
|
17631
|
+
function readProviderModelsCache() {
|
|
17632
|
+
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
|
|
17633
|
+
if (!existsSync8(cacheFile)) {
|
|
17634
|
+
log("[connected-providers-cache] Provider-models cache file not found", { cacheFile });
|
|
17635
|
+
return null;
|
|
17636
|
+
}
|
|
17637
|
+
try {
|
|
17638
|
+
const content = readFileSync4(cacheFile, "utf-8");
|
|
17639
|
+
const data = JSON.parse(content);
|
|
17640
|
+
log("[connected-providers-cache] Read provider-models cache", {
|
|
17641
|
+
providerCount: Object.keys(data.models).length,
|
|
17642
|
+
updatedAt: data.updatedAt
|
|
17643
|
+
});
|
|
17644
|
+
return data;
|
|
17645
|
+
} catch (err) {
|
|
17646
|
+
log("[connected-providers-cache] Error reading provider-models cache", { error: String(err) });
|
|
17647
|
+
return null;
|
|
17648
|
+
}
|
|
17647
17649
|
}
|
|
17648
|
-
|
|
17649
|
-
|
|
17650
|
-
|
|
17651
|
-
return existsSync8(cacheFile);
|
|
17652
|
-
}
|
|
17653
|
-
function writeProviderModelsCache(data) {
|
|
17654
|
-
ensureCacheDir2();
|
|
17655
|
-
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
|
|
17656
|
-
const cacheData = {
|
|
17657
|
-
...data,
|
|
17658
|
-
updatedAt: new Date().toISOString()
|
|
17659
|
-
};
|
|
17660
|
-
try {
|
|
17661
|
-
writeFileSync2(cacheFile, JSON.stringify(cacheData, null, 2));
|
|
17662
|
-
log("[connected-providers-cache] Provider-models cache written", {
|
|
17663
|
-
providerCount: Object.keys(data.models).length
|
|
17664
|
-
});
|
|
17665
|
-
} catch (err) {
|
|
17666
|
-
log("[connected-providers-cache] Error writing provider-models cache", { error: String(err) });
|
|
17650
|
+
function hasProviderModelsCache() {
|
|
17651
|
+
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
|
|
17652
|
+
return existsSync8(cacheFile);
|
|
17667
17653
|
}
|
|
17668
|
-
|
|
17669
|
-
|
|
17670
|
-
|
|
17671
|
-
|
|
17672
|
-
|
|
17654
|
+
function writeProviderModelsCache(data) {
|
|
17655
|
+
ensureCacheDir2();
|
|
17656
|
+
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
|
|
17657
|
+
const cacheData = {
|
|
17658
|
+
...data,
|
|
17659
|
+
updatedAt: new Date().toISOString()
|
|
17660
|
+
};
|
|
17661
|
+
try {
|
|
17662
|
+
writeFileSync2(cacheFile, JSON.stringify(cacheData, null, 2));
|
|
17663
|
+
log("[connected-providers-cache] Provider-models cache written", {
|
|
17664
|
+
providerCount: Object.keys(data.models).length
|
|
17665
|
+
});
|
|
17666
|
+
} catch (err) {
|
|
17667
|
+
log("[connected-providers-cache] Error writing provider-models cache", { error: String(err) });
|
|
17668
|
+
}
|
|
17673
17669
|
}
|
|
17674
|
-
|
|
17675
|
-
|
|
17676
|
-
|
|
17677
|
-
|
|
17678
|
-
|
|
17679
|
-
|
|
17680
|
-
|
|
17681
|
-
|
|
17682
|
-
|
|
17683
|
-
|
|
17684
|
-
|
|
17685
|
-
|
|
17670
|
+
async function updateConnectedProvidersCache(client) {
|
|
17671
|
+
if (!client?.provider?.list) {
|
|
17672
|
+
log("[connected-providers-cache] client.provider.list not available");
|
|
17673
|
+
return;
|
|
17674
|
+
}
|
|
17675
|
+
try {
|
|
17676
|
+
const result = await client.provider.list();
|
|
17677
|
+
const connected = result.data?.connected ?? [];
|
|
17678
|
+
log("[connected-providers-cache] Fetched connected providers", {
|
|
17679
|
+
count: connected.length,
|
|
17680
|
+
providers: connected
|
|
17681
|
+
});
|
|
17682
|
+
writeConnectedProvidersCache(connected);
|
|
17683
|
+
const modelsByProvider = {};
|
|
17684
|
+
const allProviders = result.data?.all ?? [];
|
|
17685
|
+
for (const provider of allProviders) {
|
|
17686
|
+
if (provider.models) {
|
|
17687
|
+
const modelIds = Object.keys(provider.models);
|
|
17688
|
+
if (modelIds.length > 0) {
|
|
17689
|
+
modelsByProvider[provider.id] = modelIds;
|
|
17690
|
+
}
|
|
17686
17691
|
}
|
|
17687
17692
|
}
|
|
17693
|
+
log("[connected-providers-cache] Extracted models from provider list", {
|
|
17694
|
+
providerCount: Object.keys(modelsByProvider).length,
|
|
17695
|
+
totalModels: Object.values(modelsByProvider).reduce((sum, ids) => sum + ids.length, 0)
|
|
17696
|
+
});
|
|
17697
|
+
writeProviderModelsCache({
|
|
17698
|
+
models: modelsByProvider,
|
|
17699
|
+
connected
|
|
17700
|
+
});
|
|
17701
|
+
} catch (err) {
|
|
17702
|
+
log("[connected-providers-cache] Error updating cache", { error: String(err) });
|
|
17688
17703
|
}
|
|
17689
|
-
log("[connected-providers-cache] Extracted models from provider list", {
|
|
17690
|
-
providerCount: Object.keys(modelsByProvider).length,
|
|
17691
|
-
totalModels: Object.values(modelsByProvider).reduce((sum, ids) => sum + ids.length, 0)
|
|
17692
|
-
});
|
|
17693
|
-
writeProviderModelsCache({
|
|
17694
|
-
models: modelsByProvider,
|
|
17695
|
-
connected
|
|
17696
|
-
});
|
|
17697
|
-
} catch (err) {
|
|
17698
|
-
log("[connected-providers-cache] Error updating cache", { error: String(err) });
|
|
17699
17704
|
}
|
|
17705
|
+
return {
|
|
17706
|
+
readConnectedProvidersCache,
|
|
17707
|
+
hasConnectedProvidersCache,
|
|
17708
|
+
readProviderModelsCache,
|
|
17709
|
+
hasProviderModelsCache,
|
|
17710
|
+
writeProviderModelsCache,
|
|
17711
|
+
updateConnectedProvidersCache
|
|
17712
|
+
};
|
|
17700
17713
|
}
|
|
17714
|
+
var defaultConnectedProvidersCacheStore = createConnectedProvidersCacheStore(() => getOmoOpenCodeCacheDir());
|
|
17715
|
+
var {
|
|
17716
|
+
readConnectedProvidersCache,
|
|
17717
|
+
hasConnectedProvidersCache,
|
|
17718
|
+
readProviderModelsCache,
|
|
17719
|
+
hasProviderModelsCache,
|
|
17720
|
+
writeProviderModelsCache,
|
|
17721
|
+
updateConnectedProvidersCache
|
|
17722
|
+
} = defaultConnectedProvidersCacheStore;
|
|
17701
17723
|
|
|
17702
17724
|
// src/shared/model-availability.ts
|
|
17703
17725
|
init_logger();
|
|
@@ -43673,6 +43695,9 @@ async function injectContinuationPrompt(ctx, options) {
|
|
|
43673
43695
|
async function handleDetectedCompletion(ctx, input) {
|
|
43674
43696
|
const { sessionID, state: state3, loopState, directory, apiTimeoutMs } = input;
|
|
43675
43697
|
if (state3.ultrawork && !state3.verification_pending) {
|
|
43698
|
+
if (state3.verification_session_id) {
|
|
43699
|
+
ctx.client.session.abort({ path: { id: state3.verification_session_id } }).catch(() => {});
|
|
43700
|
+
}
|
|
43676
43701
|
const verificationState = loopState.markVerificationPending(sessionID);
|
|
43677
43702
|
if (!verificationState) {
|
|
43678
43703
|
log(`[${HOOK_NAME3}] Failed to transition ultrawork loop to verification`, {
|
|
@@ -43909,6 +43934,9 @@ async function handleFailedVerification(ctx, input) {
|
|
|
43909
43934
|
});
|
|
43910
43935
|
return false;
|
|
43911
43936
|
}
|
|
43937
|
+
if (state3.verification_session_id) {
|
|
43938
|
+
ctx.client.session.abort({ path: { id: state3.verification_session_id } }).catch(() => {});
|
|
43939
|
+
}
|
|
43912
43940
|
const resumedState = loopState.restartAfterFailedVerification(parentSessionID, messageCountAtStart);
|
|
43913
43941
|
if (!resumedState) {
|
|
43914
43942
|
log(`[${HOOK_NAME3}] Failed to restart loop after verification failure`, {
|
|
@@ -47514,6 +47542,7 @@ var BabysittingConfigSchema = exports_external.object({
|
|
|
47514
47542
|
});
|
|
47515
47543
|
// src/config/schema/background-task.ts
|
|
47516
47544
|
var CircuitBreakerConfigSchema = exports_external.object({
|
|
47545
|
+
enabled: exports_external.boolean().optional(),
|
|
47517
47546
|
maxToolCalls: exports_external.number().int().min(10).optional(),
|
|
47518
47547
|
windowSize: exports_external.number().int().min(5).optional(),
|
|
47519
47548
|
repetitionThresholdPercent: exports_external.number().gt(0).max(100).optional()
|
|
@@ -47712,7 +47741,8 @@ var HookNameSchema = exports_external.enum([
|
|
|
47712
47741
|
"write-existing-file-guard",
|
|
47713
47742
|
"anthropic-effort",
|
|
47714
47743
|
"hashline-read-enhancer",
|
|
47715
|
-
"read-image-resizer"
|
|
47744
|
+
"read-image-resizer",
|
|
47745
|
+
"todo-description-override"
|
|
47716
47746
|
]);
|
|
47717
47747
|
// src/config/schema/notification.ts
|
|
47718
47748
|
var NotificationConfigSchema = exports_external.object({
|
|
@@ -49115,7 +49145,7 @@ var START_WORK_TEMPLATE = `You are starting a Sisyphus work session.
|
|
|
49115
49145
|
- \`--worktree <path>\` (optional): absolute path to an existing git worktree to work in
|
|
49116
49146
|
- If specified and valid: hook pre-sets worktree_path in boulder.json
|
|
49117
49147
|
- If specified but invalid: you must run \`git worktree add <path> <branch>\` first
|
|
49118
|
-
- If omitted:
|
|
49148
|
+
- If omitted: work directly in the current project directory (no worktree)
|
|
49119
49149
|
|
|
49120
49150
|
## WHAT TO DO
|
|
49121
49151
|
|
|
@@ -49132,7 +49162,7 @@ var START_WORK_TEMPLATE = `You are starting a Sisyphus work session.
|
|
|
49132
49162
|
- If ONE plan: auto-select it
|
|
49133
49163
|
- If MULTIPLE plans: show list with timestamps, ask user to select
|
|
49134
49164
|
|
|
49135
|
-
4. **Worktree Setup** (when \`worktree_path\` not already set in boulder.json):
|
|
49165
|
+
4. **Worktree Setup** (ONLY when \`--worktree\` was explicitly specified and \`worktree_path\` not already set in boulder.json):
|
|
49136
49166
|
1. \`git worktree list --porcelain\` \u2014 see available worktrees
|
|
49137
49167
|
2. Create: \`git worktree add <absolute-path> <branch-or-HEAD>\`
|
|
49138
49168
|
3. Update boulder.json to add \`"worktree_path": "<absolute-path>"\`
|
|
@@ -49194,9 +49224,41 @@ Reading plan and beginning execution...
|
|
|
49194
49224
|
|
|
49195
49225
|
- The session_id is injected by the hook - use it directly
|
|
49196
49226
|
- Always update boulder.json BEFORE starting work
|
|
49197
|
-
-
|
|
49227
|
+
- If worktree_path is set in boulder.json, all work happens inside that worktree directory
|
|
49198
49228
|
- Read the FULL plan file before delegating any tasks
|
|
49199
|
-
- Follow atlas delegation protocols (7-section format)
|
|
49229
|
+
- Follow atlas delegation protocols (7-section format)
|
|
49230
|
+
|
|
49231
|
+
## TASK BREAKDOWN (MANDATORY)
|
|
49232
|
+
|
|
49233
|
+
After reading the plan file, you MUST decompose every plan task into granular, implementation-level sub-steps and register ALL of them as task/todo items BEFORE starting any work.
|
|
49234
|
+
|
|
49235
|
+
**How to break down**:
|
|
49236
|
+
- Each plan checkbox item (e.g., \`- [ ] Add user authentication\`) must be split into concrete, actionable sub-tasks
|
|
49237
|
+
- Sub-tasks should be specific enough that each one touches a clear set of files/functions
|
|
49238
|
+
- Include: file to modify, what to change, expected behavior, and how to verify
|
|
49239
|
+
- Do NOT leave any task vague \u2014 "implement feature X" is NOT acceptable; "add validateToken() to src/auth/middleware.ts that checks JWT expiry and returns 401" IS acceptable
|
|
49240
|
+
|
|
49241
|
+
**Example breakdown**:
|
|
49242
|
+
Plan task: \`- [ ] Add rate limiting to API\`
|
|
49243
|
+
\u2192 Todo items:
|
|
49244
|
+
1. Create \`src/middleware/rate-limiter.ts\` with sliding window algorithm (max 100 req/min per IP)
|
|
49245
|
+
2. Add RateLimiter middleware to \`src/app.ts\` router chain, before auth middleware
|
|
49246
|
+
3. Add rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining) to response in \`rate-limiter.ts\`
|
|
49247
|
+
4. Add test: verify 429 response after exceeding limit in \`src/middleware/rate-limiter.test.ts\`
|
|
49248
|
+
5. Add test: verify headers are present on normal responses
|
|
49249
|
+
|
|
49250
|
+
Register these as task/todo items so progress is tracked and visible throughout the session.
|
|
49251
|
+
|
|
49252
|
+
## WORKTREE COMPLETION
|
|
49253
|
+
|
|
49254
|
+
When working in a worktree (\`worktree_path\` is set in boulder.json) and ALL plan tasks are complete:
|
|
49255
|
+
1. Commit all remaining changes in the worktree
|
|
49256
|
+
2. Switch to the main working directory (the original repo, NOT the worktree)
|
|
49257
|
+
3. Merge the worktree branch into the current branch: \`git merge <worktree-branch>\`
|
|
49258
|
+
4. If merge succeeds, clean up: \`git worktree remove <worktree-path>\`
|
|
49259
|
+
5. Remove the boulder.json state
|
|
49260
|
+
|
|
49261
|
+
This is the DEFAULT behavior when \`--worktree\` was used. Skip merge only if the user explicitly instructs otherwise (e.g., asks to create a PR instead).`;
|
|
49200
49262
|
|
|
49201
49263
|
// src/features/builtin-commands/templates/handoff.ts
|
|
49202
49264
|
var HANDOFF_TEMPLATE = `# Handoff Command
|
|
@@ -55062,6 +55124,46 @@ function createReadImageResizerHook(_ctx) {
|
|
|
55062
55124
|
}
|
|
55063
55125
|
};
|
|
55064
55126
|
}
|
|
55127
|
+
// src/hooks/todo-description-override/description.ts
|
|
55128
|
+
var TODOWRITE_DESCRIPTION = `Use this tool to create and manage a structured task list for tracking progress on multi-step work.
|
|
55129
|
+
|
|
55130
|
+
## Todo Format (MANDATORY)
|
|
55131
|
+
|
|
55132
|
+
Each todo title MUST encode four elements: WHERE, WHY, HOW, and EXPECTED RESULT.
|
|
55133
|
+
|
|
55134
|
+
Format: "[WHERE] [HOW] to [WHY] \u2014 expect [RESULT]"
|
|
55135
|
+
|
|
55136
|
+
GOOD:
|
|
55137
|
+
- "src/utils/validation.ts: Add validateEmail() for input sanitization \u2014 returns boolean"
|
|
55138
|
+
- "UserService.create(): Call validateEmail() before DB insert \u2014 rejects invalid emails with 400"
|
|
55139
|
+
- "validation.test.ts: Add test for missing @ sign \u2014 expect validateEmail('foo') to return false"
|
|
55140
|
+
|
|
55141
|
+
BAD:
|
|
55142
|
+
- "Implement email validation" (where? how? what result?)
|
|
55143
|
+
- "Add dark mode" (this is a feature, not a todo)
|
|
55144
|
+
- "Fix auth" (what file? what changes? what's expected?)
|
|
55145
|
+
|
|
55146
|
+
## Granularity Rules
|
|
55147
|
+
|
|
55148
|
+
Each todo MUST be a single atomic action completable in 1-3 tool calls. If it needs more, split it.
|
|
55149
|
+
|
|
55150
|
+
**Size test**: Can you complete this todo by editing one file or running one command? If not, it's too big.
|
|
55151
|
+
|
|
55152
|
+
## Task Management
|
|
55153
|
+
- One in_progress at a time. Complete it before starting the next.
|
|
55154
|
+
- Mark completed immediately after finishing each item.
|
|
55155
|
+
- Skip this tool for single trivial tasks (one-step, obvious action).`;
|
|
55156
|
+
|
|
55157
|
+
// src/hooks/todo-description-override/hook.ts
|
|
55158
|
+
function createTodoDescriptionOverrideHook() {
|
|
55159
|
+
return {
|
|
55160
|
+
"tool.definition": async (input, output) => {
|
|
55161
|
+
if (input.toolID === "todowrite") {
|
|
55162
|
+
output.description = TODOWRITE_DESCRIPTION;
|
|
55163
|
+
}
|
|
55164
|
+
}
|
|
55165
|
+
};
|
|
55166
|
+
}
|
|
55065
55167
|
// src/hooks/anthropic-effort/hook.ts
|
|
55066
55168
|
var OPUS_4_6_PATTERN = /claude-opus-4[-.]6/i;
|
|
55067
55169
|
function isClaudeProvider(providerID, modelID) {
|
|
@@ -77406,6 +77508,7 @@ function createToolGuardHooks(args) {
|
|
|
77406
77508
|
const hashlineReadEnhancer = isHookEnabled("hashline-read-enhancer") ? safeHook("hashline-read-enhancer", () => createHashlineReadEnhancerHook(ctx, { hashline_edit: { enabled: pluginConfig.hashline_edit ?? false } })) : null;
|
|
77407
77509
|
const jsonErrorRecovery = isHookEnabled("json-error-recovery") ? safeHook("json-error-recovery", () => createJsonErrorRecoveryHook(ctx)) : null;
|
|
77408
77510
|
const readImageResizer = isHookEnabled("read-image-resizer") ? safeHook("read-image-resizer", () => createReadImageResizerHook(ctx)) : null;
|
|
77511
|
+
const todoDescriptionOverride = isHookEnabled("todo-description-override") ? safeHook("todo-description-override", () => createTodoDescriptionOverrideHook()) : null;
|
|
77409
77512
|
return {
|
|
77410
77513
|
commentChecker,
|
|
77411
77514
|
toolOutputTruncator,
|
|
@@ -77417,7 +77520,8 @@ function createToolGuardHooks(args) {
|
|
|
77417
77520
|
writeExistingFileGuard,
|
|
77418
77521
|
hashlineReadEnhancer,
|
|
77419
77522
|
jsonErrorRecovery,
|
|
77420
|
-
readImageResizer
|
|
77523
|
+
readImageResizer,
|
|
77524
|
+
todoDescriptionOverride
|
|
77421
77525
|
};
|
|
77422
77526
|
}
|
|
77423
77527
|
|
|
@@ -77946,6 +78050,7 @@ var DEFAULT_MESSAGE_STALENESS_TIMEOUT_MS = 1800000;
|
|
|
77946
78050
|
var DEFAULT_MAX_TOOL_CALLS = 200;
|
|
77947
78051
|
var DEFAULT_CIRCUIT_BREAKER_WINDOW_SIZE = 20;
|
|
77948
78052
|
var DEFAULT_CIRCUIT_BREAKER_REPETITION_THRESHOLD_PERCENT = 80;
|
|
78053
|
+
var DEFAULT_CIRCUIT_BREAKER_ENABLED = true;
|
|
77949
78054
|
var MIN_RUNTIME_BEFORE_STALE_MS = 30000;
|
|
77950
78055
|
var MIN_IDLE_TIME_MS = 5000;
|
|
77951
78056
|
var POLLING_INTERVAL_MS = 3000;
|
|
@@ -78500,27 +78605,52 @@ async function checkAndInterruptStaleTasks(args) {
|
|
|
78500
78605
|
// src/features/background-agent/loop-detector.ts
|
|
78501
78606
|
function resolveCircuitBreakerSettings(config4) {
|
|
78502
78607
|
return {
|
|
78608
|
+
enabled: config4?.circuitBreaker?.enabled ?? DEFAULT_CIRCUIT_BREAKER_ENABLED,
|
|
78503
78609
|
maxToolCalls: config4?.circuitBreaker?.maxToolCalls ?? config4?.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS,
|
|
78504
78610
|
windowSize: config4?.circuitBreaker?.windowSize ?? DEFAULT_CIRCUIT_BREAKER_WINDOW_SIZE,
|
|
78505
78611
|
repetitionThresholdPercent: config4?.circuitBreaker?.repetitionThresholdPercent ?? DEFAULT_CIRCUIT_BREAKER_REPETITION_THRESHOLD_PERCENT
|
|
78506
78612
|
};
|
|
78507
78613
|
}
|
|
78508
|
-
function recordToolCall(window, toolName, settings) {
|
|
78509
|
-
const previous = window?.
|
|
78510
|
-
const
|
|
78614
|
+
function recordToolCall(window, toolName, settings, toolInput) {
|
|
78615
|
+
const previous = window?.toolSignatures ?? [];
|
|
78616
|
+
const signature = createToolCallSignature(toolName, toolInput);
|
|
78617
|
+
const toolSignatures = [...previous, signature].slice(-settings.windowSize);
|
|
78511
78618
|
return {
|
|
78512
|
-
|
|
78619
|
+
toolSignatures,
|
|
78513
78620
|
windowSize: settings.windowSize,
|
|
78514
78621
|
thresholdPercent: settings.repetitionThresholdPercent
|
|
78515
78622
|
};
|
|
78516
78623
|
}
|
|
78624
|
+
function sortObject2(obj) {
|
|
78625
|
+
if (obj === null || obj === undefined)
|
|
78626
|
+
return obj;
|
|
78627
|
+
if (typeof obj !== "object")
|
|
78628
|
+
return obj;
|
|
78629
|
+
if (Array.isArray(obj))
|
|
78630
|
+
return obj.map(sortObject2);
|
|
78631
|
+
const sorted = {};
|
|
78632
|
+
const keys = Object.keys(obj).sort();
|
|
78633
|
+
for (const key of keys) {
|
|
78634
|
+
sorted[key] = sortObject2(obj[key]);
|
|
78635
|
+
}
|
|
78636
|
+
return sorted;
|
|
78637
|
+
}
|
|
78638
|
+
function createToolCallSignature(toolName, toolInput) {
|
|
78639
|
+
if (toolInput === undefined || toolInput === null) {
|
|
78640
|
+
return toolName;
|
|
78641
|
+
}
|
|
78642
|
+
if (Object.keys(toolInput).length === 0) {
|
|
78643
|
+
return toolName;
|
|
78644
|
+
}
|
|
78645
|
+
return `${toolName}::${JSON.stringify(sortObject2(toolInput))}`;
|
|
78646
|
+
}
|
|
78517
78647
|
function detectRepetitiveToolUse(window) {
|
|
78518
|
-
if (!window || window.
|
|
78648
|
+
if (!window || window.toolSignatures.length === 0) {
|
|
78519
78649
|
return { triggered: false };
|
|
78520
78650
|
}
|
|
78521
78651
|
const counts = new Map;
|
|
78522
|
-
for (const
|
|
78523
|
-
counts.set(
|
|
78652
|
+
for (const signature of window.toolSignatures) {
|
|
78653
|
+
counts.set(signature, (counts.get(signature) ?? 0) + 1);
|
|
78524
78654
|
}
|
|
78525
78655
|
let repeatedTool;
|
|
78526
78656
|
let repeatedCount = 0;
|
|
@@ -78530,7 +78660,7 @@ function detectRepetitiveToolUse(window) {
|
|
|
78530
78660
|
repeatedCount = count;
|
|
78531
78661
|
}
|
|
78532
78662
|
}
|
|
78533
|
-
const sampleSize = window.
|
|
78663
|
+
const sampleSize = window.toolSignatures.length;
|
|
78534
78664
|
const minimumSampleSize = Math.min(window.windowSize, Math.ceil(window.windowSize * window.thresholdPercent / 100));
|
|
78535
78665
|
if (sampleSize < minimumSampleSize) {
|
|
78536
78666
|
return { triggered: false };
|
|
@@ -78541,7 +78671,7 @@ function detectRepetitiveToolUse(window) {
|
|
|
78541
78671
|
}
|
|
78542
78672
|
return {
|
|
78543
78673
|
triggered: true,
|
|
78544
|
-
toolName: repeatedTool,
|
|
78674
|
+
toolName: repeatedTool.split("::")[0],
|
|
78545
78675
|
repeatedCount,
|
|
78546
78676
|
sampleSize,
|
|
78547
78677
|
thresholdPercent: window.thresholdPercent
|
|
@@ -79247,23 +79377,25 @@ class BackgroundManager {
|
|
|
79247
79377
|
task.progress.lastTool = partInfo.tool;
|
|
79248
79378
|
const circuitBreaker = resolveCircuitBreakerSettings(this.config);
|
|
79249
79379
|
if (partInfo.tool) {
|
|
79250
|
-
task.progress.toolCallWindow = recordToolCall(task.progress.toolCallWindow, partInfo.tool, circuitBreaker);
|
|
79251
|
-
|
|
79252
|
-
|
|
79253
|
-
|
|
79254
|
-
|
|
79255
|
-
|
|
79256
|
-
|
|
79257
|
-
|
|
79258
|
-
|
|
79259
|
-
|
|
79260
|
-
|
|
79261
|
-
|
|
79262
|
-
|
|
79263
|
-
|
|
79264
|
-
|
|
79265
|
-
|
|
79266
|
-
|
|
79380
|
+
task.progress.toolCallWindow = recordToolCall(task.progress.toolCallWindow, partInfo.tool, circuitBreaker, partInfo.state?.input);
|
|
79381
|
+
if (circuitBreaker.enabled) {
|
|
79382
|
+
const loopDetection = detectRepetitiveToolUse(task.progress.toolCallWindow);
|
|
79383
|
+
if (loopDetection.triggered) {
|
|
79384
|
+
log("[background-agent] Circuit breaker: repetitive tool usage detected", {
|
|
79385
|
+
taskId: task.id,
|
|
79386
|
+
agent: task.agent,
|
|
79387
|
+
sessionID,
|
|
79388
|
+
toolName: loopDetection.toolName,
|
|
79389
|
+
repeatedCount: loopDetection.repeatedCount,
|
|
79390
|
+
sampleSize: loopDetection.sampleSize,
|
|
79391
|
+
thresholdPercent: loopDetection.thresholdPercent
|
|
79392
|
+
});
|
|
79393
|
+
this.cancelTask(task.id, {
|
|
79394
|
+
source: "circuit-breaker",
|
|
79395
|
+
reason: `Subagent repeatedly called ${loopDetection.toolName} ${loopDetection.repeatedCount}/${loopDetection.sampleSize} times in the recent tool-call window (${loopDetection.thresholdPercent}% threshold). This usually indicates an infinite loop. The task was automatically cancelled to prevent excessive token usage.`
|
|
79396
|
+
});
|
|
79397
|
+
return;
|
|
79398
|
+
}
|
|
79267
79399
|
}
|
|
79268
79400
|
}
|
|
79269
79401
|
const maxToolCalls = circuitBreaker.maxToolCalls;
|
|
@@ -97707,7 +97839,10 @@ function createPluginInterface(args) {
|
|
|
97707
97839
|
"tool.execute.after": createToolExecuteAfterHandler3({
|
|
97708
97840
|
ctx,
|
|
97709
97841
|
hooks: hooks2
|
|
97710
|
-
})
|
|
97842
|
+
}),
|
|
97843
|
+
"tool.definition": async (input, output) => {
|
|
97844
|
+
await hooks2.todoDescriptionOverride?.["tool.definition"]?.(input, output);
|
|
97845
|
+
}
|
|
97711
97846
|
};
|
|
97712
97847
|
}
|
|
97713
97848
|
|
|
@@ -23,6 +23,7 @@ export declare function createCoreHooks(args: {
|
|
|
23
23
|
hashlineReadEnhancer: ReturnType<typeof import("../../hooks").createHashlineReadEnhancerHook> | null;
|
|
24
24
|
jsonErrorRecovery: ReturnType<typeof import("../../hooks").createJsonErrorRecoveryHook> | null;
|
|
25
25
|
readImageResizer: ReturnType<typeof import("../../hooks").createReadImageResizerHook> | null;
|
|
26
|
+
todoDescriptionOverride: ReturnType<typeof import("../../hooks").createTodoDescriptionOverrideHook> | null;
|
|
26
27
|
contextWindowMonitor: ReturnType<typeof import("../../hooks").createContextWindowMonitorHook> | null;
|
|
27
28
|
preemptiveCompaction: ReturnType<typeof import("../../hooks").createPreemptiveCompactionHook> | null;
|
|
28
29
|
sessionRecovery: ReturnType<typeof import("../../hooks").createSessionRecoveryHook> | null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { HookName, OhMyOpenCodeConfig } from "../../config";
|
|
2
2
|
import type { ModelCacheState } from "../../plugin-state";
|
|
3
3
|
import type { PluginContext } from "../types";
|
|
4
|
-
import { createCommentCheckerHooks, createToolOutputTruncatorHook, createDirectoryAgentsInjectorHook, createDirectoryReadmeInjectorHook, createEmptyTaskResponseDetectorHook, createRulesInjectorHook, createTasksTodowriteDisablerHook, createWriteExistingFileGuardHook, createHashlineReadEnhancerHook, createReadImageResizerHook, createJsonErrorRecoveryHook } from "../../hooks";
|
|
4
|
+
import { createCommentCheckerHooks, createToolOutputTruncatorHook, createDirectoryAgentsInjectorHook, createDirectoryReadmeInjectorHook, createEmptyTaskResponseDetectorHook, createRulesInjectorHook, createTasksTodowriteDisablerHook, createWriteExistingFileGuardHook, createHashlineReadEnhancerHook, createReadImageResizerHook, createJsonErrorRecoveryHook, createTodoDescriptionOverrideHook } from "../../hooks";
|
|
5
5
|
export type ToolGuardHooks = {
|
|
6
6
|
commentChecker: ReturnType<typeof createCommentCheckerHooks> | null;
|
|
7
7
|
toolOutputTruncator: ReturnType<typeof createToolOutputTruncatorHook> | null;
|
|
@@ -14,6 +14,7 @@ export type ToolGuardHooks = {
|
|
|
14
14
|
hashlineReadEnhancer: ReturnType<typeof createHashlineReadEnhancerHook> | null;
|
|
15
15
|
jsonErrorRecovery: ReturnType<typeof createJsonErrorRecoveryHook> | null;
|
|
16
16
|
readImageResizer: ReturnType<typeof createReadImageResizerHook> | null;
|
|
17
|
+
todoDescriptionOverride: ReturnType<typeof createTodoDescriptionOverrideHook> | null;
|
|
17
18
|
};
|
|
18
19
|
export declare function createToolGuardHooks(args: {
|
|
19
20
|
ctx: PluginContext;
|
|
@@ -10,36 +10,33 @@ interface ProviderModelsCache {
|
|
|
10
10
|
connected: string[];
|
|
11
11
|
updatedAt: string;
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
13
|
+
export declare function createConnectedProvidersCacheStore(getCacheDir?: () => string): {
|
|
14
|
+
readConnectedProvidersCache: () => string[] | null;
|
|
15
|
+
hasConnectedProvidersCache: () => boolean;
|
|
16
|
+
readProviderModelsCache: () => ProviderModelsCache | null;
|
|
17
|
+
hasProviderModelsCache: () => boolean;
|
|
18
|
+
writeProviderModelsCache: (data: {
|
|
19
|
+
models: Record<string, string[]>;
|
|
20
|
+
connected: string[];
|
|
21
|
+
}) => void;
|
|
22
|
+
updateConnectedProvidersCache: (client: {
|
|
23
|
+
provider?: {
|
|
24
|
+
list?: () => Promise<{
|
|
25
|
+
data?: {
|
|
26
|
+
connected?: string[];
|
|
27
|
+
all?: Array<{
|
|
28
|
+
id: string;
|
|
29
|
+
models?: Record<string, unknown>;
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
32
|
+
}>;
|
|
33
|
+
};
|
|
34
|
+
}) => Promise<void>;
|
|
35
|
+
};
|
|
36
|
+
export declare const readConnectedProvidersCache: () => string[] | null, hasConnectedProvidersCache: () => boolean, readProviderModelsCache: () => ProviderModelsCache | null, hasProviderModelsCache: () => boolean, writeProviderModelsCache: (data: {
|
|
35
37
|
models: Record<string, string[]>;
|
|
36
38
|
connected: string[];
|
|
37
|
-
}):
|
|
38
|
-
/**
|
|
39
|
-
* Update the connected providers cache by fetching from the client.
|
|
40
|
-
* Also updates the provider-models cache with model lists per provider.
|
|
41
|
-
*/
|
|
42
|
-
export declare function updateConnectedProvidersCache(client: {
|
|
39
|
+
}) => void, updateConnectedProvidersCache: (client: {
|
|
43
40
|
provider?: {
|
|
44
41
|
list?: () => Promise<{
|
|
45
42
|
data?: {
|
|
@@ -51,5 +48,5 @@ export declare function updateConnectedProvidersCache(client: {
|
|
|
51
48
|
};
|
|
52
49
|
}>;
|
|
53
50
|
};
|
|
54
|
-
})
|
|
51
|
+
}) => Promise<void>;
|
|
55
52
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-opencode",
|
|
3
|
-
"version": "3.12.
|
|
3
|
+
"version": "3.12.1",
|
|
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",
|
|
@@ -76,17 +76,17 @@
|
|
|
76
76
|
"typescript": "^5.7.3"
|
|
77
77
|
},
|
|
78
78
|
"optionalDependencies": {
|
|
79
|
-
"oh-my-opencode-darwin-arm64": "3.12.
|
|
80
|
-
"oh-my-opencode-darwin-x64": "3.12.
|
|
81
|
-
"oh-my-opencode-darwin-x64-baseline": "3.12.
|
|
82
|
-
"oh-my-opencode-linux-arm64": "3.12.
|
|
83
|
-
"oh-my-opencode-linux-arm64-musl": "3.12.
|
|
84
|
-
"oh-my-opencode-linux-x64": "3.12.
|
|
85
|
-
"oh-my-opencode-linux-x64-baseline": "3.12.
|
|
86
|
-
"oh-my-opencode-linux-x64-musl": "3.12.
|
|
87
|
-
"oh-my-opencode-linux-x64-musl-baseline": "3.12.
|
|
88
|
-
"oh-my-opencode-windows-x64": "3.12.
|
|
89
|
-
"oh-my-opencode-windows-x64-baseline": "3.12.
|
|
79
|
+
"oh-my-opencode-darwin-arm64": "3.12.1",
|
|
80
|
+
"oh-my-opencode-darwin-x64": "3.12.1",
|
|
81
|
+
"oh-my-opencode-darwin-x64-baseline": "3.12.1",
|
|
82
|
+
"oh-my-opencode-linux-arm64": "3.12.1",
|
|
83
|
+
"oh-my-opencode-linux-arm64-musl": "3.12.1",
|
|
84
|
+
"oh-my-opencode-linux-x64": "3.12.1",
|
|
85
|
+
"oh-my-opencode-linux-x64-baseline": "3.12.1",
|
|
86
|
+
"oh-my-opencode-linux-x64-musl": "3.12.1",
|
|
87
|
+
"oh-my-opencode-linux-x64-musl-baseline": "3.12.1",
|
|
88
|
+
"oh-my-opencode-windows-x64": "3.12.1",
|
|
89
|
+
"oh-my-opencode-windows-x64-baseline": "3.12.1"
|
|
90
90
|
},
|
|
91
91
|
"overrides": {
|
|
92
92
|
"@opencode-ai/sdk": "^1.2.24"
|