Skip to content

Commit a35d314

Browse files
gracefullightgithub-actions[bot]
authored andcommitted
chore(deps): update oh-my-agent skills
1 parent ed9061d commit a35d314

180 files changed

Lines changed: 4988 additions & 1227 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/agents/pm-planner.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ CHARTER_CHECK:
3737

3838
1. **Gather**: Requirements (users, features, constraints, deployment target)
3939
2. **Analyze**: Technical feasibility using codebase analysis
40-
3. **Contracts**: Define API contracts (save to `.agents/skills/_shared/core/api-contracts/`)
40+
3. **Contracts**: Define API contracts using template `.agents/skills/_shared/core/api-contracts/template.md`; save the generated contract to `.agents/results/api-contracts/` (run artifact) or `docs/plans/contracts/` (durable spec)
4141
4. **Decompose**: Break into tasks with agent, title, acceptance criteria, priority (P0-P3), dependencies
4242
5. **Output**: Save to `.agents/results/plan-{sessionId}.json`
4343

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"$schema": "./agent-variant.schema.json",
3+
"vendor": "opencode",
4+
"destDir": ".opencode/agents",
5+
"modelDefault": "opencode-go/deepseek-v4-flash",
6+
"toolsDefault": [],
7+
"protocolPath": ".agents/skills/_shared/runtime/execution-protocols/opencode.md",
8+
"agents": {
9+
"backend-engineer": {},
10+
"frontend-engineer": {},
11+
"db-engineer": {},
12+
"debug-investigator": {},
13+
"architecture-reviewer": {},
14+
"tf-infra-engineer": {},
15+
"mobile-engineer": {},
16+
"pm-planner": {},
17+
"qa-reviewer": {},
18+
"docs-curator": {},
19+
"refactor-engineer": {},
20+
"research-explorer": {}
21+
}
22+
}

.agents/config/defaults.yaml

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ agent_defaults:
2222
db: { model: "openai/gpt-5.5", effort: "high" }
2323
debug: { model: "openai/gpt-5.5", effort: "high" }
2424
tf-infra: { model: "openai/gpt-5.5", effort: "high" }
25-
explore: { model: "google/gemini-3.1-flash-lite" }
25+
explore: { model: "openai/gpt-5.4-mini", effort: "low" }
2626

2727
runtime_profiles:
2828
claude:
@@ -55,23 +55,8 @@ runtime_profiles:
5555
tf-infra: { model: "openai/gpt-5.5", effort: "high" }
5656
explore: { model: "openai/gpt-5.4-mini", effort: "low" }
5757

58-
gemini:
59-
description: "Gemini — Google AI Pro"
60-
agent_defaults:
61-
orchestrator: { model: "google/gemini-3-flash" }
62-
architecture: { model: "google/gemini-3.1-pro-preview", thinking: true }
63-
qa: { model: "google/gemini-3-flash", thinking: true }
64-
pm: { model: "google/gemini-3-flash" }
65-
backend: { model: "google/gemini-3-flash", thinking: true }
66-
frontend: { model: "google/gemini-3-flash", thinking: true }
67-
mobile: { model: "google/gemini-3-flash", thinking: true }
68-
db: { model: "google/gemini-3-flash", thinking: true }
69-
debug: { model: "google/gemini-3-flash", thinking: true }
70-
tf-infra: { model: "google/gemini-3-flash", thinking: true }
71-
explore: { model: "google/gemini-3.1-flash-lite" }
72-
7358
mixed:
74-
description: "Mixed — role-optimal vendors per agent (Claude for orchestration/QA/PM, Codex for impl, Gemini for explore)"
59+
description: "Mixed — role-optimal vendors per agent (Claude for orchestration/QA/PM, Codex for impl/explore)"
7560
agent_defaults:
7661
orchestrator: { model: "anthropic/claude-sonnet-4-6" }
7762
architecture: { model: "anthropic/claude-opus-4-7" }
@@ -83,7 +68,7 @@ runtime_profiles:
8368
db: { model: "openai/gpt-5.5", effort: "high" }
8469
debug: { model: "openai/gpt-5.5", effort: "high" }
8570
tf-infra: { model: "openai/gpt-5.5", effort: "high" }
86-
explore: { model: "google/gemini-3.1-flash-lite" }
71+
explore: { model: "openai/gpt-5.4-mini", effort: "low" }
8772

8873
antigravity:
8974
description: "Antigravity CLI (agy) — nominal Gemini 3.1 Pro for impl/architecture, Gemini 3.5 Flash for orchestration & explore (agy 1.0 has no --model flag; model is config-driven)"

.agents/hooks/core/agentmemory-client.ts

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ function endpointUrl(): string | null {
3232

3333
let reachable: boolean | null = null;
3434

35+
/**
36+
* Test-only: clear the memoized reachability probe so cases that point
37+
* `AGENTMEMORY_URL` at different endpoints don't leak a cached verdict into each
38+
* other (the probe is intentionally memoized once per process at runtime).
39+
*/
40+
export function _resetReachableCache(): void {
41+
reachable = null;
42+
}
43+
3544
function requestAgentMemory(
3645
baseUrl: string,
3746
path: string,
@@ -142,15 +151,63 @@ export interface RecalledFact {
142151

143152
interface SearchResult {
144153
score?: number;
154+
timestamp?: unknown;
155+
created_at?: unknown;
145156
observation?: {
146157
narrative?: unknown;
147158
facts?: unknown;
148159
title?: unknown;
149160
type?: unknown;
161+
timestamp?: unknown;
162+
created_at?: unknown;
150163
};
151164
}
152165

153-
function parseSearchResults(body: string, k: number): RecalledFact[] {
166+
/**
167+
* Recall TTL: facts older than this many days are dropped from the snapshot so
168+
* stale, long-resolved decisions stop rehydrating every boundary. Default 30
169+
* days; set `OMA_RECALL_MAX_AGE_DAYS=0` (or a non-positive value) to disable.
170+
* Returns the max age in ms, or null when disabled.
171+
*/
172+
function recallMaxAgeMs(): number | null {
173+
const raw = process.env.OMA_RECALL_MAX_AGE_DAYS;
174+
const days = raw === undefined ? 30 : Number(raw);
175+
if (!Number.isFinite(days) || days <= 0) return null;
176+
return days * 24 * 60 * 60 * 1000;
177+
}
178+
179+
/**
180+
* Best-effort timestamp extraction from a search result. AgentMemory's response
181+
* envelope is not contractually fixed across versions, so several candidate
182+
* field names / locations are probed. Numeric epoch seconds are normalised to
183+
* ms. Returns null when no parseable timestamp is present — callers then keep
184+
* the fact (TTL filtering is fail-open, never dropping facts of unknown age).
185+
*/
186+
function extractTimestampMs(entry: SearchResult): number | null {
187+
const obs = entry.observation ?? {};
188+
const candidates: unknown[] = [
189+
obs.timestamp,
190+
obs.created_at,
191+
entry.timestamp,
192+
entry.created_at,
193+
];
194+
for (const candidate of candidates) {
195+
if (typeof candidate === "number" && Number.isFinite(candidate)) {
196+
return candidate < 1e12 ? candidate * 1000 : candidate;
197+
}
198+
if (typeof candidate === "string" && candidate.trim()) {
199+
const parsed = Date.parse(candidate);
200+
if (Number.isFinite(parsed)) return parsed;
201+
}
202+
}
203+
return null;
204+
}
205+
206+
export function parseSearchResults(
207+
body: string,
208+
k: number,
209+
nowMs: number = Date.now(),
210+
): RecalledFact[] {
154211
let parsed: { results?: unknown };
155212
try {
156213
parsed = JSON.parse(body) as { results?: unknown };
@@ -164,12 +221,20 @@ function parseSearchResults(body: string, k: number): RecalledFact[] {
164221
return Number.isFinite(raw) ? raw : 1;
165222
})();
166223

224+
const maxAgeMs = recallMaxAgeMs();
225+
const cutoffMs = maxAgeMs === null ? null : nowMs - maxAgeMs;
226+
167227
const facts: RecalledFact[] = [];
168228
for (const entry of parsed.results as SearchResult[]) {
169229
const score = typeof entry.score === "number" ? entry.score : 0;
170230
// Raw `/observe` envelopes score near-zero (~0.006); enriched facts score
171231
// in the single digits. Drop the noise floor so the snapshot stays useful.
172232
if (score < minScore) continue;
233+
// TTL: drop facts older than the cutoff (fail-open on unknown age).
234+
if (cutoffMs !== null) {
235+
const tsMs = extractTimestampMs(entry);
236+
if (tsMs !== null && tsMs < cutoffMs) continue;
237+
}
173238
const obs = entry.observation ?? {};
174239
const narrative =
175240
typeof obs.narrative === "string" && obs.narrative.trim()
@@ -198,11 +263,14 @@ export async function recallFacts(
198263
k = 5,
199264
): Promise<RecalledFact[]> {
200265
if (!query.trim()) return [];
201-
if (!(await isAgentMemoryReachable())) return [];
202-
const url = endpointUrl();
203-
if (!url) return [];
204-
266+
// The whole body is guarded so this honors its "never throws" contract: the
267+
// reachability probe and endpoint resolution can throw under load (e.g. a
268+
// socket error from the shared daemon), and an unguarded throw here blanks the
269+
// boundary snapshot the hook would otherwise emit. Degrade to local-only.
205270
try {
271+
if (!(await isAgentMemoryReachable())) return [];
272+
const url = endpointUrl();
273+
if (!url) return [];
206274
const response = await requestAgentMemory(url, "/agentmemory/search", {
207275
method: "POST",
208276
headers: { "content-type": "application/json" },
@@ -222,11 +290,13 @@ export async function observeWithTimeout(payload: {
222290
source: string;
223291
projectDir?: string;
224292
}): Promise<boolean> {
225-
if (!(await isAgentMemoryReachable())) return false;
226-
const url = endpointUrl();
227-
if (!url) return false;
228-
293+
// Fully guarded (best-effort, never throws): the reachability probe and
294+
// endpoint resolution can throw under load, and a throw here must not abort
295+
// the hook that fired the observe.
229296
try {
297+
if (!(await isAgentMemoryReachable())) return false;
298+
const url = endpointUrl();
299+
if (!url) return false;
230300
// AgentMemory's /observe expects a hook-event envelope
231301
// (hookType, sessionId, project, cwd, timestamp) carrying the content.
232302
const cwd = payload.projectDir ?? process.cwd();

.agents/hooks/core/constants.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,22 @@ export const VENDORS = [
3535
"codex",
3636
"commandcode",
3737
"cursor",
38-
"gemini",
3938
"grok",
39+
"kimi",
4040
"kiro",
4141
"pi",
4242
"qwen",
4343
] as const;
44+
45+
/**
46+
* Fallback session id used when no vendor session id can be resolved from the
47+
* hook stdin (`getSessionId`). It is shared across BOTH the keyword-detector and
48+
* persistent-mode hooks so the two stay in sync.
49+
*
50+
* A persistent-mode state file keyed to this value cannot be isolated per
51+
* session: any later session whose id ALSO resolves to the fallback would
52+
* inherit the stale workflow's persistent block (a cross-session false
53+
* positive). Both hooks therefore refuse to write — and refuse to act on —
54+
* persistent state under this id.
55+
*/
56+
export const UNKNOWN_SESSION_ID = "unknown";

.agents/hooks/core/hook-output.ts

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Vendor-specific hook output builders.
2-
// Each runtime (Claude Code, Codex CLI, Cursor, Gemini CLI, Qwen Code)
2+
// Each runtime (Claude Code, Codex CLI, Cursor, Qwen Code)
33
// expects a slightly different stdout JSON shape; centralize the dialect
44
// translation here so individual hooks can stay vendor-agnostic.
55

@@ -49,13 +49,6 @@ export function makePromptOutput(
4949
additionalContext,
5050
},
5151
});
52-
case "gemini":
53-
return JSON.stringify({
54-
hookSpecificOutput: {
55-
hookEventName: "BeforeAgent",
56-
additionalContext,
57-
},
58-
});
5952
case "grok":
6053
// Grok hook context injection: return additionalContext; Grok may surface
6154
// it via hook annotations or ignore for prompt events. State side-effects
@@ -64,6 +57,10 @@ export function makePromptOutput(
6457
case "kiro":
6558
// Kiro CLI adds stdout directly to the agent context for prompt hooks.
6659
return additionalContext;
60+
case "kimi":
61+
// Kimi Code CLI: a blockable hook that exits 0 has its stdout appended to
62+
// the model context (kimi.com/code/docs hooks). Plain text injects directly.
63+
return additionalContext;
6764
case "pi":
6865
// pi (Earendil) reads this via the in-process bridge in
6966
// `.pi/extensions/oma/index.ts`, which lifts `additionalContext` into the
@@ -93,9 +90,6 @@ export function makeBlockOutput(vendor: Vendor, reason: string): string {
9390
// agy Stop: `decision:"continue"` re-enters the loop (= block the stop);
9491
// `reason` is injected as a system message. (Any other value allows stop.)
9592
return JSON.stringify({ decision: "continue", reason });
96-
case "gemini":
97-
// Gemini AfterAgent uses "deny" to reject response and force retry
98-
return JSON.stringify({ decision: "deny", reason });
9993
case "pi":
10094
// pi has no stop-blocking event (agent_end is notification-only), so
10195
// persistent-mode never runs under pi. This shape mirrors pi's native
@@ -105,6 +99,20 @@ export function makeBlockOutput(vendor: Vendor, reason: string): string {
10599
// Grok Stop hooks are generally advisory. Emit block decision + rich
106100
// stderr message (persistent-mode already prints the reason to stderr).
107101
return JSON.stringify({ decision: "block", reason });
102+
case "kimi":
103+
// Kimi documents two blocking mechanisms: exit 2 + stderr, and a JSON
104+
// `hookSpecificOutput.permissionDecision: "deny"` response. The oma hook
105+
// router always exits 0 and writes the dialect to stdout, so we emit the
106+
// JSON form. We also include the Claude-style `{decision:"block"}` keys so
107+
// whichever shape Kimi's Stop handler honours, persistent-mode re-enters.
108+
return JSON.stringify({
109+
decision: "block",
110+
reason,
111+
hookSpecificOutput: {
112+
permissionDecision: "deny",
113+
permissionDecisionReason: reason,
114+
},
115+
});
108116
}
109117
}
110118

@@ -113,16 +121,6 @@ export function makePreToolOutput(
113121
updatedInput: Record<string, unknown>,
114122
): string {
115123
switch (vendor) {
116-
case "gemini":
117-
// Official BeforeTool rewrite contract (geminicli.com/docs/hooks/reference):
118-
// `hookSpecificOutput.tool_input` merges with and overrides the model's
119-
// arguments. There is no "rewrite" decision value (only allow/deny).
120-
return JSON.stringify({
121-
hookSpecificOutput: {
122-
hookEventName: "BeforeTool",
123-
tool_input: updatedInput,
124-
},
125-
});
126124
case "cursor":
127125
return JSON.stringify({
128126
updated_input: updatedInput,
@@ -134,6 +132,7 @@ export function makePreToolOutput(
134132
case "claude":
135133
case "codex":
136134
case "commandcode":
135+
case "kimi":
137136
case "kiro":
138137
case "qwen":
139138
// Codex requires `permissionDecision: "allow"` alongside `updatedInput`

0 commit comments

Comments
 (0)