Skip to content

Commit 705723b

Browse files
fix: repair skill and run lifecycle config
1 parent ca39429 commit 705723b

19 files changed

Lines changed: 271 additions & 27 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ Docs: https://docs.openclaw.ai
170170
### Fixes
171171

172172
- Memory: close temp SQLite handles before failed atomic reindex cleanup and retry Windows EBUSY/EPERM/EACCES temp file removals, so `memory index --force` does not abort or leave temp sidecars on locked filesystems. Fixes #79708. Thanks @LobsterFarmerAmp and @hclsys.
173+
- Skills CLI: normalize object-shaped `metadata.openclaw.requires.config` entries by their `path` so skill info/status no longer renders `[object Object]` or blocks valid config requirements. Fixes #79488. Thanks @sebrinass.
174+
- Agents/subagents: honor `OPENCLAW_SPAWN_ALLOWLIST` and `SPAWN_ALLOWLIST` as fallback target-agent allowlists for `sessions_spawn` when no config allowlist is set. Fixes #79490. Thanks @baptisterou.
175+
- Control UI/WebChat: map backend agent run ids back to the originating chat run so context-overflow lifecycle errors clear the active run instead of leaving Stop and queued `/new` stuck. Fixes #79525. Thanks @dmak.
176+
- CLI/status: prefer systemd `is-active` when `systemctl show` returns a stale failed state, so active Gateway user services report as running. Fixes #79515. Thanks @holgergruenhagen.
173177
- Agents/CLI: add an explicit `reseedFromRawTranscriptWhenUncompacted` backend opt-in so safe invalidated CLI sessions can reseed from a bounded raw OpenClaw transcript tail before compaction while auth-boundary resets remain no-raw. Fixes #79713. (#79764) Thanks @hclsys.
174178
- Agents/CLI: handle resumed CLI JSONL output and bound supervisor output buffering so resumed runs stay readable without letting noisy child output grow unbounded.
175179
- Codex app-server: honor per-call `timeoutMs`, configured `image_generate` timeouts, and media image-understanding timeouts for dynamic tool calls, capped at 600000 ms, so slow image generation and image analysis no longer fail at the 30s bridge default. Fixes #79810. Thanks @omarshahine.

docs/gateway/config-tools.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ Experimental built-in tool flags. Default off unless a strict-agentic GPT-5 auto
379379
```
380380

381381
- `model`: default model for spawned sub-agents. If omitted, sub-agents inherit the caller's model.
382-
- `allowAgents`: default allowlist of target agent ids for `sessions_spawn` when the requester agent does not set its own `subagents.allowAgents` (`["*"]` = any; default: same agent only).
382+
- `allowAgents`: default allowlist of target agent ids for `sessions_spawn` when the requester agent does not set its own `subagents.allowAgents` (`["*"]` = any; default: same agent only). If neither config path is set, `OPENCLAW_SPAWN_ALLOWLIST` or `SPAWN_ALLOWLIST` can provide a fallback (`*`, comma-separated ids, or a JSON array).
383383
- `runTimeoutSeconds`: default timeout (seconds) for `sessions_spawn` when the tool call omits `runTimeoutSeconds`. `0` means no timeout.
384384
- Per-subagent tool policy: `tools.subagents.tools.allow` / `tools.subagents.tools.deny`.
385385

docs/tools/creating-skills.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,13 @@ For how skills are loaded and prioritized, see [Skills](/tools/skills).
8686

8787
The YAML frontmatter supports these fields:
8888

89-
| Field | Required | Description |
90-
| ----------------------------------- | -------- | -------------------------------------------------------------- |
91-
| `name` | Yes | Unique identifier using lowercase letters, digits, and hyphens |
92-
| `description` | Yes | One-line description shown to the agent |
93-
| `metadata.openclaw.os` | No | OS filter (`["darwin"]`, `["linux"]`, etc.) |
94-
| `metadata.openclaw.requires.bins` | No | Required binaries on PATH |
95-
| `metadata.openclaw.requires.config` | No | Required config keys |
89+
| Field | Required | Description |
90+
| ----------------------------------- | -------- | ------------------------------------------------------------------------------ |
91+
| `name` | Yes | Unique identifier using lowercase letters, digits, and hyphens |
92+
| `description` | Yes | One-line description shown to the agent |
93+
| `metadata.openclaw.os` | No | OS filter (`["darwin"]`, `["linux"]`, etc.) |
94+
| `metadata.openclaw.requires.bins` | No | Required binaries on PATH |
95+
| `metadata.openclaw.requires.config` | No | Required config keys (`"path"` strings or `{ path, access, purpose }` objects) |
9696

9797
## Best practices
9898

docs/tools/skills.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ Fields under `metadata.openclaw`:
244244
<ParamField path="requires.env" type="string[]">
245245
Env var must exist or be provided in config.
246246
</ParamField>
247-
<ParamField path="requires.config" type="string[]">
248-
List of `openclaw.json` paths that must be truthy.
247+
<ParamField path="requires.config" type='Array<string | { path: string; access?: string; purpose?: string }>'>
248+
List of `openclaw.json` paths that must be truthy. Object entries are accepted for descriptive metadata; OpenClaw uses their `path` value for eligibility checks.
249249
</ParamField>
250250
<ParamField path="primaryEnv" type="string">
251251
Env var name associated with `skills.entries.<name>.apiKey`.

docs/tools/subagents.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,9 @@ See [Configuration reference](/gateway/configuration-reference) and
286286
<ParamField path="agents.defaults.subagents.allowAgents" type="string[]">
287287
Default target-agent allowlist used when the requester agent does not set its own `subagents.allowAgents`.
288288
</ParamField>
289+
<ParamField path="SPAWN_ALLOWLIST" type="string">
290+
Environment fallback used only when neither per-agent nor default config sets `allowAgents`. Accepts `*`, comma-separated agent ids, or a JSON string array. `OPENCLAW_SPAWN_ALLOWLIST` is also accepted and takes precedence.
291+
</ParamField>
289292
<ParamField path="agents.defaults.subagents.requireAgentId" type="boolean" default="false">
290293
Block `sessions_spawn` calls that omit `agentId` (forces explicit profile selection). Per-agent override: `agents.list[].subagents.requireAgentId`.
291294
</ParamField>

src/agents/acp-spawn.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ import {
8282
} from "./subagent-capabilities.js";
8383
import { getSubagentDepthFromSessionStore } from "./subagent-depth.js";
8484
import { countActiveRunsForSession, getSubagentRunByChildSessionKey } from "./subagent-registry.js";
85-
import { resolveSubagentTargetPolicy } from "./subagent-target-policy.js";
85+
import {
86+
resolveConfiguredSubagentAllowAgents,
87+
resolveSubagentTargetPolicy,
88+
} from "./subagent-target-policy.js";
8689
import { resolveInternalSessionKey, resolveMainSessionAlias } from "./tools/sessions-helpers.js";
8790

8891
const log = createSubsystemLogger("agents/acp-spawn");
@@ -793,9 +796,11 @@ function resolveAcpSubagentEnvelopeState(params: {
793796
requesterAgentId,
794797
targetAgentId: params.targetAgentId,
795798
requestedAgentId: params.requestedAgentId,
796-
allowAgents:
797-
resolveAgentConfig(params.cfg, requesterAgentId)?.subagents?.allowAgents ??
798-
params.cfg.agents?.defaults?.subagents?.allowAgents,
799+
allowAgents: resolveConfiguredSubagentAllowAgents({
800+
agentAllowAgents: resolveAgentConfig(params.cfg, requesterAgentId)?.subagents?.allowAgents,
801+
defaultAllowAgents: params.cfg.agents?.defaults?.subagents?.allowAgents,
802+
env: process.env,
803+
}),
799804
});
800805
if (!targetPolicy.ok) {
801806
return {

src/agents/openclaw-tools.subagents.sessions-spawn.allowlist.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
2+
import { withEnvAsync } from "../test-utils/env.js";
23
import {
34
createSubagentSpawnTestConfig,
45
loadSubagentSpawnModuleForTest,
@@ -133,6 +134,21 @@ describe("subagent spawn allowlist + sandbox guards", () => {
133134
});
134135
});
135136

137+
it("falls back to SPAWN_ALLOWLIST when config omits allowAgents", async () => {
138+
setConfig({
139+
agents: {
140+
list: [{ id: "main" }, { id: "beta" }],
141+
},
142+
});
143+
144+
const result = await withEnvAsync({ SPAWN_ALLOWLIST: "*" }, () => spawn({ agentId: "beta" }));
145+
146+
expect(result).toMatchObject({
147+
status: "accepted",
148+
childSessionKey: expect.stringMatching(/^agent:beta:subagent:/),
149+
});
150+
});
151+
136152
it("allows any agent when allowlist contains *", async () => {
137153
setConfig({
138154
agents: {

src/agents/skills.buildworkspaceskillstatus.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,28 @@ describe("buildWorkspaceSkillStatus", () => {
147147
expect(skill.install[0]?.bins).toEqual(["fakebin"]);
148148
});
149149

150+
it("normalizes object-shaped config requirements from skill metadata", async () => {
151+
const workspaceDir = await createTempWorkspaceDir();
152+
await writeSkill({
153+
dir: path.join(workspaceDir, "skills", "object-config-skill"),
154+
name: "object-config-skill",
155+
description: "Object config metadata",
156+
metadata:
157+
'{"openclaw":{"requires":{"config":[{"path":"browser.enabled","access":"read","purpose":"Check browser config"}]}}}',
158+
});
159+
160+
const report = buildWorkspaceSkillStatus(workspaceDir, {
161+
managedSkillsDir: path.join(workspaceDir, ".managed"),
162+
config: { browser: { enabled: true } },
163+
});
164+
const skill = requireReportedSkill(report, "object-config-skill");
165+
166+
expect(skill.eligible).toBe(true);
167+
expect(skill.requirements.config).toEqual(["browser.enabled"]);
168+
expect(skill.missing.config).toEqual([]);
169+
expect(skill.configChecks).toEqual([{ path: "browser.enabled", satisfied: true }]);
170+
});
171+
150172
it("respects OS-gated skills", () => {
151173
const entry = makeEntry({
152174
name: "os-skill",

src/agents/subagent-spawn.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ import { getSubagentDepthFromSessionStore } from "./subagent-depth.js";
2929
import { buildSubagentInitialUserMessage } from "./subagent-initial-user-message.js";
3030
import { countActiveRunsForSession, registerSubagentRun } from "./subagent-registry.js";
3131
import { resolveSubagentSpawnAcceptedNote } from "./subagent-spawn-accepted-note.js";
32-
import { resolveSubagentTargetPolicy } from "./subagent-target-policy.js";
32+
import {
33+
resolveConfiguredSubagentAllowAgents,
34+
resolveSubagentTargetPolicy,
35+
} from "./subagent-target-policy.js";
3336
export {
3437
SUBAGENT_SPAWN_ACCEPTED_NOTE,
3538
SUBAGENT_SPAWN_SESSION_ACCEPTED_NOTE,
@@ -805,9 +808,11 @@ export async function spawnSubagentDirect(
805808
requesterAgentId,
806809
targetAgentId,
807810
requestedAgentId,
808-
allowAgents:
809-
resolveAgentConfig(cfg, requesterAgentId)?.subagents?.allowAgents ??
810-
cfg?.agents?.defaults?.subagents?.allowAgents,
811+
allowAgents: resolveConfiguredSubagentAllowAgents({
812+
agentAllowAgents: resolveAgentConfig(cfg, requesterAgentId)?.subagents?.allowAgents,
813+
defaultAllowAgents: cfg?.agents?.defaults?.subagents?.allowAgents,
814+
env: process.env,
815+
}),
811816
});
812817
if (!targetPolicy.ok) {
813818
return {

src/agents/subagent-target-policy.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, expect, it } from "vitest";
22
import {
3+
resolveConfiguredSubagentAllowAgents,
34
resolveSubagentAllowedTargetIds,
45
resolveSubagentTargetPolicy,
56
} from "./subagent-target-policy.js";
@@ -64,4 +65,33 @@ describe("subagent target policy", () => {
6465
allowedIds: ["planner"],
6566
});
6667
});
68+
69+
it("uses SPAWN_ALLOWLIST as a fallback allowlist when config omits one", () => {
70+
expect(
71+
resolveConfiguredSubagentAllowAgents({
72+
env: { SPAWN_ALLOWLIST: " planner, checker " },
73+
}),
74+
).toEqual(["planner", "checker"]);
75+
expect(
76+
resolveConfiguredSubagentAllowAgents({
77+
env: { OPENCLAW_SPAWN_ALLOWLIST: '["research", "*"]', SPAWN_ALLOWLIST: "ignored" },
78+
}),
79+
).toEqual(["research", "*"]);
80+
});
81+
82+
it("keeps explicit config ahead of SPAWN_ALLOWLIST", () => {
83+
expect(
84+
resolveConfiguredSubagentAllowAgents({
85+
agentAllowAgents: ["agent-specific"],
86+
defaultAllowAgents: ["default"],
87+
env: { SPAWN_ALLOWLIST: "*" },
88+
}),
89+
).toEqual(["agent-specific"]);
90+
expect(
91+
resolveConfiguredSubagentAllowAgents({
92+
defaultAllowAgents: ["default"],
93+
env: { SPAWN_ALLOWLIST: "*" },
94+
}),
95+
).toEqual(["default"]);
96+
});
6797
});

0 commit comments

Comments
 (0)