@wirecat/cli-core 0.436.0 → 0.437.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,7 +5,29 @@ Notable changes to `@wirecat/cli-core` (`@wirecat/cli-messaging` up to 0.261.0,
5
5
  minor version may break callers, and says how under "Changed — may break callers". `pnpm docs:check` checks the
6
6
  shape of this file.
7
7
 
8
- ## Unreleased
8
+ ## 0.437.0 — 26.10.2026
9
+
10
+ ### Added
11
+
12
+ - **Runs started by OpenClaw or Hermes Agent name that agent, and a Hermes run its session.** OpenClaw's agent sets
13
+ `OPENCLAW_SHELL=exec` in the commands it runs: such a run's `agent` is `openclaw`, with no session, since OpenClaw
14
+ gives none. `tui-local`, what the owner types in OpenClaw's terminal view, stays the owner's. A run started from a
15
+ chat message adds `from_chat: true`; the sender and chat ids OpenClaw passes in `OPENCLAW_CHANNEL_CONTEXT` are never
16
+ read into the record. Hermes was already named `hermes-agent` from `AI_AGENT`; its `HERMES_SESSION_ID` is now the
17
+ session, so a task claim from Hermes is held by `hermes-agent:<session>` as one from Claude Code is by its session.
18
+ A reader of `run.json` that refuses unknown keys must accept the optional `agent.from_chat`.
19
+ - **`validateSkill` accepts OpenClaw's declarations as a mapping at `metadata.openclaw`.** OpenClaw and ClawHub read a
20
+ skill's required programs and its install entry (`requires.bins`, `install: [{ kind: node, package, bins }]`) only
21
+ from an object under that key, so a tool's SKILL.md can now carry them and still pass its own skill check. Every
22
+ other metadata key stays a string, as the Agent Skills specification asks; `openclaw` as a list, a string or null is
23
+ still refused.
24
+
25
+ ### Changed — may break callers
26
+
27
+ - **cli-core needs `@wirecat/cli-base` 0.59.** The peer range moves from `>=0.57.0 <0.58.0` to `>=0.59.0 <0.60.0`. A
28
+ CLI that pins cli-base moves its pin with this release. cli-base 0.59 brings `skill install --for hermes`, which
29
+ gives Hermes Agent the skill, and a `hermes` entry in `skill status`; every CLI on cli-core gets both. cli-base 0.58
30
+ renames the release lock to `wirecat-release-<package>.lock` and drops `leemour` from the shared spelling list.
9
31
 
10
32
  ## 0.436.0 — 26.10.2026
11
33
 
@@ -7,7 +7,7 @@ export declare const authorOf: (env: NodeJS.ProcessEnv, asOwner?: boolean) => st
7
7
  /**
8
8
  * Who holds a task claim — one agent session, not the agent's name, so two sessions of one agent do not share one
9
9
  * claim (owner, NEED-1119). `--holder`, then `WIRECAT_HOLDER`, then the agent's own session id where it sets one
10
- * (Claude Code, Codex, OpenCode), else the agent's name.
10
+ * (Claude Code, Codex, OpenCode, Hermes), else the agent's name.
11
11
  */
12
12
  export declare const holderOf: (env: NodeJS.ProcessEnv, flag?: string) => string;
13
13
  /**
@@ -9,7 +9,7 @@ export const authorOf = (env, asOwner) => asOwner ? "owner" : (agentOf(env)?.kin
9
9
  /**
10
10
  * Who holds a task claim — one agent session, not the agent's name, so two sessions of one agent do not share one
11
11
  * claim (owner, NEED-1119). `--holder`, then `WIRECAT_HOLDER`, then the agent's own session id where it sets one
12
- * (Claude Code, Codex, OpenCode), else the agent's name.
12
+ * (Claude Code, Codex, OpenCode, Hermes), else the agent's name.
13
13
  */
14
14
  export const holderOf = (env, flag) => {
15
15
  const given = flag?.trim() || env.WIRECAT_HOLDER?.trim();
@@ -7059,6 +7059,7 @@ export declare const ENTITIES: {
7059
7059
  readonly agent: v.OptionalSchema<v.StrictObjectSchema<{
7060
7060
  readonly kind: v.StringSchema<undefined>;
7061
7061
  readonly session_id: v.NullableSchema<v.StringSchema<undefined>, undefined>;
7062
+ readonly from_chat: v.OptionalSchema<v.LiteralSchema<true, undefined>, undefined>;
7062
7063
  }, undefined>, undefined>;
7063
7064
  readonly error: v.OptionalSchema<v.StrictObjectSchema<{
7064
7065
  readonly class: v.StringSchema<undefined>;
@@ -7121,6 +7122,7 @@ export declare const ENTITIES: {
7121
7122
  readonly agent: v.OptionalSchema<v.StrictObjectSchema<{
7122
7123
  readonly kind: v.StringSchema<undefined>;
7123
7124
  readonly session_id: v.NullableSchema<v.StringSchema<undefined>, undefined>;
7125
+ readonly from_chat: v.OptionalSchema<v.LiteralSchema<true, undefined>, undefined>;
7124
7126
  }, undefined>, undefined>;
7125
7127
  readonly error: v.OptionalSchema<v.StrictObjectSchema<{
7126
7128
  readonly class: v.StringSchema<undefined>;
@@ -7182,6 +7184,7 @@ export declare const ENTITIES: {
7182
7184
  readonly agent: v.OptionalSchema<v.StrictObjectSchema<{
7183
7185
  readonly kind: v.StringSchema<undefined>;
7184
7186
  readonly session_id: v.NullableSchema<v.StringSchema<undefined>, undefined>;
7187
+ readonly from_chat: v.OptionalSchema<v.LiteralSchema<true, undefined>, undefined>;
7185
7188
  }, undefined>, undefined>;
7186
7189
  readonly error: v.OptionalSchema<v.StrictObjectSchema<{
7187
7190
  readonly class: v.StringSchema<undefined>;
@@ -34,6 +34,7 @@ export declare const recordedRun: v.StrictObjectSchema<{
34
34
  readonly agent: v.OptionalSchema<v.StrictObjectSchema<{
35
35
  readonly kind: v.StringSchema<undefined>;
36
36
  readonly session_id: v.NullableSchema<v.StringSchema<undefined>, undefined>;
37
+ readonly from_chat: v.OptionalSchema<v.LiteralSchema<true, undefined>, undefined>;
37
38
  }, undefined>, undefined>;
38
39
  readonly error: v.OptionalSchema<v.StrictObjectSchema<{
39
40
  readonly class: v.StringSchema<undefined>;
@@ -96,6 +97,7 @@ export declare const runDetails: v.StrictObjectSchema<{
96
97
  readonly agent: v.OptionalSchema<v.StrictObjectSchema<{
97
98
  readonly kind: v.StringSchema<undefined>;
98
99
  readonly session_id: v.NullableSchema<v.StringSchema<undefined>, undefined>;
100
+ readonly from_chat: v.OptionalSchema<v.LiteralSchema<true, undefined>, undefined>;
99
101
  }, undefined>, undefined>;
100
102
  readonly error: v.OptionalSchema<v.StrictObjectSchema<{
101
103
  readonly class: v.StringSchema<undefined>;
@@ -157,6 +159,7 @@ export declare const runHit: v.StrictObjectSchema<{
157
159
  readonly agent: v.OptionalSchema<v.StrictObjectSchema<{
158
160
  readonly kind: v.StringSchema<undefined>;
159
161
  readonly session_id: v.NullableSchema<v.StringSchema<undefined>, undefined>;
162
+ readonly from_chat: v.OptionalSchema<v.LiteralSchema<true, undefined>, undefined>;
160
163
  }, undefined>, undefined>;
161
164
  readonly error: v.OptionalSchema<v.StrictObjectSchema<{
162
165
  readonly class: v.StringSchema<undefined>;
@@ -28,7 +28,7 @@ export const recordedRun = v.strictObject({
28
28
  cli_command: v.optional(text),
29
29
  argv: v.optional(v.array(text)),
30
30
  unknown_command: v.optional(text),
31
- agent: v.optional(v.strictObject({ kind: text, session_id: v.nullable(text) })),
31
+ agent: v.optional(v.strictObject({ kind: text, session_id: v.nullable(text), from_chat: v.optional(v.literal(true)) })),
32
32
  error: v.optional(v.strictObject({
33
33
  class: text,
34
34
  code: v.optional(text),
@@ -5,12 +5,15 @@ export declare const REDACTED = "[redacted]";
5
5
  export interface RunAgent {
6
6
  kind: string;
7
7
  session_id: string | null;
8
+ /** Set, and only `true`, when a chat message started the agent's turn; who sent it is never kept. */
9
+ from_chat?: true;
8
10
  }
9
11
  /**
10
12
  * The agent is read from what each one sets in the processes it starts: Claude Code `CLAUDECODE` and
11
- * `CLAUDE_CODE_SESSION_ID`, Codex `CODEX_THREAD_ID`, OpenCode `OPENCODE` and `OPENCODE_SESSION_ID`, Gemini CLI
12
- * `GEMINI_CLI`, Cursor `CURSOR_AGENT` — the last two give their commands no session id. `AI_AGENT` (Claude Code,
13
- * OpenCode) carries a version, `claude-code_2-1-296_agent`, so only the name before it is kept.
13
+ * `CLAUDE_CODE_SESSION_ID`, Codex `CODEX_THREAD_ID`, OpenCode `OPENCODE` and `OPENCODE_SESSION_ID`, Hermes
14
+ * `AI_AGENT=hermes-agent` and `HERMES_SESSION_ID`, Gemini CLI `GEMINI_CLI`, Cursor `CURSOR_AGENT`, OpenClaw
15
+ * `OPENCLAW_SHELL=exec` — the last three give their commands no session id. `AI_AGENT` (Claude Code, OpenCode, Hermes)
16
+ * carries a version, `claude-code_2-1-296_agent`, so only the name before it is kept.
14
17
  */
15
18
  export declare const agentOf: (env: NodeJS.ProcessEnv) => RunAgent | undefined;
16
19
  export interface MaskedArgv {
@@ -2,9 +2,10 @@ import { isContentKey } from "@wirecat/cli-base/standards";
2
2
  export const REDACTED = "[redacted]";
3
3
  /**
4
4
  * The agent is read from what each one sets in the processes it starts: Claude Code `CLAUDECODE` and
5
- * `CLAUDE_CODE_SESSION_ID`, Codex `CODEX_THREAD_ID`, OpenCode `OPENCODE` and `OPENCODE_SESSION_ID`, Gemini CLI
6
- * `GEMINI_CLI`, Cursor `CURSOR_AGENT` — the last two give their commands no session id. `AI_AGENT` (Claude Code,
7
- * OpenCode) carries a version, `claude-code_2-1-296_agent`, so only the name before it is kept.
5
+ * `CLAUDE_CODE_SESSION_ID`, Codex `CODEX_THREAD_ID`, OpenCode `OPENCODE` and `OPENCODE_SESSION_ID`, Hermes
6
+ * `AI_AGENT=hermes-agent` and `HERMES_SESSION_ID`, Gemini CLI `GEMINI_CLI`, Cursor `CURSOR_AGENT`, OpenClaw
7
+ * `OPENCLAW_SHELL=exec` — the last three give their commands no session id. `AI_AGENT` (Claude Code, OpenCode, Hermes)
8
+ * carries a version, `claude-code_2-1-296_agent`, so only the name before it is kept.
8
9
  */
9
10
  export const agentOf = (env) => {
10
11
  const named = env.AI_AGENT?.trim().split("_")[0];
@@ -13,22 +14,29 @@ export const agentOf = (env) => {
13
14
  (env.CODEX_THREAD_ID || env.CODEX_SESSION_ID || env.CODEX_SANDBOX ? "codex" : undefined) ??
14
15
  (env.GEMINI_CLI ? "gemini-cli" : undefined) ??
15
16
  (env.OPENCODE ? "opencode" : undefined) ??
16
- (env.CURSOR_AGENT ? "cursor" : undefined);
17
+ (env.CURSOR_AGENT ? "cursor" : undefined) ??
18
+ // Only `exec` is the agent's own command: OpenClaw also sets `tui-local` for what the owner types in its TUI.
19
+ (env.OPENCLAW_SHELL?.trim() === "exec" ? "openclaw" : undefined);
17
20
  if (!kind)
18
21
  return undefined;
19
22
  const session = sessionOf(kind, env);
20
- return { kind, session_id: session && SESSION.test(session) ? session : null };
23
+ return {
24
+ kind,
25
+ session_id: session && SESSION.test(session) ? session : null,
26
+ // OpenClaw passes the sender and chat ids of a chat-started run in this variable; only its presence is kept.
27
+ ...(kind === "openclaw" && env.OPENCLAW_CHANNEL_CONTEXT?.trim() ? { from_chat: true } : {}),
28
+ };
21
29
  };
22
30
  const SESSION = /^[A-Za-z0-9._:-]{1,128}$/;
31
+ const SESSION_VARIABLE = {
32
+ "claude-code": "CLAUDE_CODE_SESSION_ID",
33
+ codex: "CODEX_THREAD_ID",
34
+ opencode: "OPENCODE_SESSION_ID",
35
+ "hermes-agent": "HERMES_SESSION_ID",
36
+ };
23
37
  const sessionOf = (kind, env) => {
24
- const value = kind === "claude-code"
25
- ? env.CLAUDE_CODE_SESSION_ID
26
- : kind === "codex"
27
- ? env.CODEX_THREAD_ID
28
- : kind === "opencode"
29
- ? env.OPENCODE_SESSION_ID
30
- : undefined;
31
- return value?.trim() || undefined;
38
+ const variable = SESSION_VARIABLE[kind];
39
+ return (variable === undefined ? undefined : env[variable]?.trim()) || undefined;
32
40
  };
33
41
  const plainWord = (word) => /^[A-Za-z][A-Za-z0-9.-]{0,39}$/.test(word);
34
42
  /**
@@ -1,6 +1,9 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { parseDocument } from "yaml";
4
+ // The Agent Skills specification asks for string values; OpenClaw and ClawHub read their install and requirement
5
+ // declarations only from an object at `metadata.openclaw`, so that one key is a mapping.
6
+ const isMap = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4
7
  export const validateSkill = (content, options = {}) => {
5
8
  const problems = [];
6
9
  const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content);
@@ -36,11 +39,9 @@ export const validateSkill = (content, options = {}) => {
36
39
  if (!known.has(key))
37
40
  problems.push(`unsupported frontmatter field: ${key}`);
38
41
  if (fields.metadata !== undefined &&
39
- (!fields.metadata ||
40
- typeof fields.metadata !== "object" ||
41
- Array.isArray(fields.metadata) ||
42
- Object.values(fields.metadata).some((value) => typeof value !== "string")))
43
- problems.push("skill metadata must contain string values");
42
+ (!isMap(fields.metadata) ||
43
+ Object.entries(fields.metadata).some(([key, value]) => key === "openclaw" ? !isMap(value) : typeof value !== "string")))
44
+ problems.push("skill metadata must contain string values, and metadata.openclaw a mapping");
44
45
  if (options.version && fields.metadata?.version !== options.version)
45
46
  problems.push("emitted skill version must match the binary");
46
47
  const body = content.slice(match[0].length);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.436.0",
3
+ "version": "0.437.0",
4
4
  "description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Viacheslav Ptsarev",
@@ -217,7 +217,7 @@
217
217
  "@types/node": "^22.20.5",
218
218
  "@types/pngjs": "6.0.5",
219
219
  "@vitest/coverage-v8": "^5.0.5",
220
- "@wirecat/cli-base": "0.57.0",
220
+ "@wirecat/cli-base": "0.59.0",
221
221
  "@wirecat/cli-core-legacy-tags": "npm:@wirecat/cli-core@0.414.0",
222
222
  "commander": "15.0.0",
223
223
  "cspell": "10.4.0",
@@ -237,7 +237,7 @@
237
237
  },
238
238
  "peerDependencies": {
239
239
  "@napi-rs/canvas": ">=0.1.69 <2",
240
- "@wirecat/cli-base": ">=0.57.0 <0.58.0",
240
+ "@wirecat/cli-base": ">=0.59.0 <0.60.0",
241
241
  "commander": ">=15"
242
242
  },
243
243
  "bin": {