When sessions.write_json_snapshots is disabled, Hermes only writes to
state.db (SQLite). loadHermesSession now falls back to reading from
~/.hermes/state.db when the JSON file is missing.
- Add getHermesDbPath() and loadHermesSessionFromDb() functions
- Use bun:sqlite with readonly mode, try-catch for graceful errors
- JSON file still takes priority (fast path)
- Filter messages to user/assistant/tool roles
- Convert unix timestamps to ISO 8601 strings
This commit addresses three related issues in the CLI config and setup commands:
1. Issue #531: Fix config list apiKey masking
- maskApiKeys() now checks for 'apiKey' instead of 'apiKeyEnv'
- Updated tests to use apiKey field throughout
2. Issue #532: Add config set key validation
- Reject unknown top-level keys with helpful error messages
- Reject unknown nested fields in providers/models/agents
- Reject incomplete paths and nested paths on scalar keys
- Added VALID_CONFIG_KEYS schema and validateConfigKey() function
3. Issue #533: Fix agent name double-prefix in setup
- mergeConfig() now uses _agentNameFromBinary() to normalize agent names
- 'uwf-hermes' input now produces 'hermes' key with 'uwf-hermes' command
- Added tests for prefixed agent names
All tests passing, no regressions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Breaking change: Store API keys directly in config.yaml instead of
environment variable names.
## Changes
### @uncaged/workflow-protocol
- Change ProviderConfig.apiKeyEnv: string → apiKey: string
- Update README to reflect new type
### @uncaged/workflow-util-agent
- extract.ts: Remove dotenv loading, use providerEntry.apiKey directly
- storage.ts: Update normalizeProviders to validate apiKey field
- Update error messages to reference apiKey instead of apiKeyEnv
### @uncaged/cli-workflow
- setup.ts: Write actual API key to config.yaml, not .env
- Remove apiKeyEnvName(), loadEnvFile(), saveEnvFile() functions
- Remove getEnvPath() function
- Update cmdSetup to not return envPath in result
- Update README to reflect config.yaml stores API keys
- Fix setup-validate.test.ts to not expect envPath in result
## Verification
- ✅ bun run build passes
- ✅ All tests pass (260/260 in cli-workflow, 55/55 in util-agent)
- ✅ bun run check passes (only pre-existing warnings)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add configuration management commands to uwf CLI:
- uwf config list: display all config values (masks API keys)
- uwf config get <key>: retrieve specific value using dot notation
- uwf config set <key> <value>: update config value with auto-creation
Implementation:
- New file packages/cli-workflow/src/commands/config.ts with helper functions
- Comprehensive test coverage (32 tests) in config.test.ts
- Supports nested path navigation via dot notation
- Auto-creates intermediate objects when setting new paths
- Masks apiKeyEnv values in list output for security
Resolves#526
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- moderator-reference: use nested map graph format matching evaluate.ts
- yaml-reference: use goal/procedure/output/capabilities/frontmatter fields
matching actual WorkflowPayload, not fabricated system/outputSchema
- skill.test.ts: replace hardcoded absolute path with __dirname-relative
- skill.test.ts: assert 'frontmatter' instead of 'outputSchema'
Closes#519
The ACP protocol's tool_call updates only carry a display title (not a
structured tool name) and omit rawInput for polished tools, making the
reconstructed messages unusable for step read/show.
Changes:
- hermes.ts: storePromptResult reads ~/.hermes/sessions/session_{id}.json
via loadHermesSession() instead of using ACP-reconstructed messages
- acp-client.ts: strip message/tool-call collection logic, keep only
text chunk accumulation for final response extraction
- step.ts: TurnData gains role + toolCalls fields; formatTurnBody
renders them in step read markdown output
- README: document sessions.write_json_snapshots requirement
Two fixes for 'uwf thread start solve-issue' failures:
1. json-cas 0.5.2 (npm) was missing oneOf in ALLOWED_SCHEMA_KEYS.
Published json-cas 0.5.3 with the fix, bumped all packages to ^0.5.3.
2. Semantic validator only recognized oneOf-based multi-exit schemas.
Roles using $status with enum (e.g. enum: [approved, rejected]) were
incorrectly treated as single-exit. Added isEnumMultiExit() support.
Changes:
- validate-semantic.ts: isEnumMultiExit(), getEnumStatuses(), checkSingleExitMustache()
- All package.json: @uncaged/json-cas ^0.5.2 → ^0.5.3
- validate-semantic.test.ts: 5 new enum multi-exit tests (Suite 3b)
- solve-issue-tea-worktree.test.ts: updated for current workflow structure
小橘 🍊
- Add agent discovery step to cmdSetupInteractive flow
- _promptAgentSelection: discover uwf-* binaries, auto-select if only one,
prompt user to choose if multiple, show install hints if none found
- mergeConfig: always write selected agent entry, update defaultAgent
- Known agent labels for hermes, claude-code, cursor, builtin
- 10 new tests for _agentNameFromBinary, _printAgentMenu, cmdSetup agent config
Fixes#424
The test used a fake CasRef string as frontmatter, which fails
putSchema validation when loading from YAML files. Replace with
a proper JSON Schema object.
Fixes pre-existing failures in workflow-resolution, cas-exit-code,
and thread-step-count tests.
- Move hermes ACP integration tests to __tests__/integration/
- Add test:ci script to all packages (excludes integration/)
- CI workflow uses test:ci instead of test
- bun test still runs everything (unit + integration)
Refs #510
- Remove hardcoded ~/repos/workflow paths from procedure text
- Use .worktrees/ relative to repo root instead of global path
- Add developer failed → $END exit for unrecoverable situations
- Add worktree field to reviewer rejected variant
- Fix test workflowPath to use import.meta.dirname
Refs #506
BREAKING CHANGE: StepRecord now requires startedAtMs and completedAtMs fields.
StepEntry now requires durationMs field. Old CAS data without these fields is invalid.
- Add startedAtMs/completedAtMs to StepRecord and StepNodePayload
- Add durationMs to StepEntry (computed: completedAtMs - startedAtMs)
- Update STEP_NODE_SCHEMA to require timing fields as integers
- Record Date.now() before/after agent execution in createAgent
- Show duration in thread read headers (formatStepHeader)
- Update existing test fixtures with timing fields
buildOutputFormatInstruction now detects discriminated union schemas
(oneOf with shared const/ property) and renders separate YAML
example blocks per variant, so agents see exactly which fields belong
to which outcome instead of a flat merge.
Non-discriminated oneOf/anyOf schemas fall back to the existing flat
merge behavior.
Refs #502
- evaluate() reads $status instead of status, defaults to _ when missing
- Update all YAML examples and .workflows to use $status
- Update cli-workflow resolveEvaluateArgs to use $status
- 10 moderator tests pass including new default _ test
- Single-exit roles no longer need to declare status field
Phase 1 of #499 (closes#500)
Replace all JSONata/ConditionDefinition/ConditionalEdge references with
status-based routing terminology across 8 files:
- README.md, CLAUDE.md: moderator description, key terms
- docs/architecture.md: dependency jsonata→mustache, evaluate signature
- docs/wf-stateless-design.md: type definitions, routing context
- packages/workflow-moderator/README.md: full rewrite for new API
- packages/workflow-protocol/README.md: Target type, remove Transition
- packages/workflow-dashboard/context.md: StatusEdge, graph type
- docs/builtin-agent-research.md: stale JSONata references
- Rename ConditionalEdge → StatusEdge, condition → status throughout
- Rename conditional.tsx → status.tsx, edge label shows status value
- Update trans-in/trans-out to use status field instead of condition
- Update validate to check status edges
- Align server/workflow.ts with new WorkflowPayload.graph format
- 20 dashboard tests pass
Phase 3 of #490 (closes#493)
- Migrate solve-issue.yaml, analyze-topic.yaml, debate.yaml to new format
- Add status enum field to all role frontmatter schemas
- Use {{{ }}} (triple mustache) for prompt templates with user content
- Disable mustache HTML escaping globally (prompts are plain text, not HTML)
- Add 2 new tests for HTML escape behavior
- 9 moderator tests pass
Phase 2 of #490 (closes#492)
Extract four helper functions from cmdStepRead to reduce cognitive
complexity from 27 to ≤15:
- loadStepDetail: Load and validate step detail node
- loadTurnData: Load all turn nodes and extract content
- selectTurnsForQuota: Select turns within quota (≥1 always shown)
- formatStepMarkdown: Assemble final markdown output
All 6 existing tests pass. Zero Biome warnings. CLAUDE.md compliant.
Fixes#487
Replace custom buildHistorySummary with shared buildContinuationPrompt
from workflow-agent-kit. This aligns claude-code agent with hermes agent:
- First visit: includes step content (within 32k quota)
- Re-entry: shows only steps since last visit (meta only, session has context)
Previously developer could not see reviewer's detailed feedback on
re-entry, only {"approved":false}. Now gets full review text.
Fixes#486
Implements the `uwf step read` command to render a single step's turns
as human-readable markdown with quota enforcement.
Changes:
- Implement cmdStepRead() in step.ts with quota enforcement
- Renders step metadata (hash, role, agent)
- Loads and formats turns from detail node
- Enforces quota by selecting most recent turns
- Always shows at least one turn even if it exceeds quota
- Gracefully handles steps with no detail or no turns
- Register `step read` command in cli.ts with --quota flag (default 4000)
- Add comprehensive test suite in step-read.test.ts (6 tests covering
basic functionality, quota enforcement, edge cases, and special chars)
- Update README.md CLI Reference table to include `step read`
- Update package-level README.md with command documentation and example
Closes#484
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Issue #480: The --quota flag on 'uwf thread read' was not properly
limiting output size due to an off-by-one error in selectByQuota().
Root cause:
- Items were added to selected array BEFORE checking if they would
exceed the quota
- This meant the last item that exceeded quota was still included
- Prompt deduplication tracking was mutated during quota calculation,
causing prompts to not render in final output
Fix:
- Check quota BEFORE adding items to selected array
- Always include at least one step even if it exceeds quota
- Calculate step lengths using actual rendering format
- Account for start section and separators in quota calculation
- Use temporary Set during length calculation to avoid mutating
the prompt deduplication tracking
Tests:
- Added comprehensive test suite (thread-read-quota.test.ts)
- Covers quota enforcement, boundary conditions, edge cases
- Tests interaction with --before and --start flags
- All tests pass
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Changed the exit behavior of 'uwf cas has' to return exit code 1 when
a hash doesn't exist, while preserving the JSON output {exists:false}.
This enables proper use in shell conditionals like 'if uwf cas has $HASH'.
Fixes#481
Fixed issue #469 where `uwf step list`, `uwf step show`, and `uwf thread read`
failed with "thread not active" error when called on completed threads.
The root cause was that resolveHeadHash() in shared.ts only checked threads.yaml
(active threads index) but never fell back to history.jsonl (completed threads log).
Changes:
- Updated resolveHeadHash() in shared.ts to check history.jsonl as fallback
- Changed error message from "thread not active" to "thread not found"
- Added comprehensive test coverage:
- Unit tests for resolveHeadHash() with active/completed/missing threads
- Integration tests for cmdStepList() with completed threads
- Integration tests for cmdStepShow() with completed threads
- Regression tests for cmdThreadRead() with completed threads
All commands now work identically for active and completed threads.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a role participates for the first time (e.g. committer), it previously
only received the system prompt + last step output, missing the full thread
history. This caused hallucination as the role had to guess what happened.
Changes:
- build-continuation-prompt.ts: detect first-time roles and include all
steps' meta + content for last 2-3 steps (within quota)
- context.ts: add isFirstVisit detection helper
- types.ts: add isFirstVisit field to AgentContext
- hermes.ts: pass isFirstVisit through to prompt builder
Fixes#473
Implements enhanced filtering and pagination for the `uwf thread list` command
to support workflows with large numbers of threads.
Changes:
- Add --page, --page-size parameters for pagination (default: page 1, size 20)
- Add --since, --until time filters supporting multiple formats (ISO8601, relative like "2h", "1d")
- Add --workflow filter to show threads for specific workflow
- Add --sort parameter (newest-first, oldest-first, alphabetical)
- Add pagination metadata in JSON output (page, pageSize, totalThreads, totalPages, hasMore)
- Implement parseRelativeTime() for human-friendly time expressions (1h, 30m, 2d, 1w)
- Add comprehensive unit tests for filters, pagination, and time parsing
- Update CLI help text with new parameters and examples
Fixes#471
- Add `content: string | null` to RoleStep type
- Resolve contentHash → text for the last step when building ThreadContext
- Update buildAgentPrompt to include <output> tag with step content
- Add 16k content quota with truncation
- Update tests
Previously step list showed raw CAS refs for detail fields.
Now detail is recursively expanded (like output already was),
since every turn is individually hashed and walkable.
Refs #463