- Remove UNCAGED_CAS_DIR and UNCAGED_WORKFLOW_STORAGE_ROOT env var fallbacks - Tests updated to use OCAS_DIR / UWF_STORAGE_ROOT - All docs, READMEs, scripts, workflows, skills updated - Only symlink migration code retains .uncaged paths (functional requirement) Closes #12 (Phase 5 complete)
This commit is contained in:
@@ -17,7 +17,7 @@ roles:
|
|||||||
docker run -d --name uwf-e2e-$$ \
|
docker run -d --name uwf-e2e-$$ \
|
||||||
-v "$(pwd):/workspace:ro" \
|
-v "$(pwd):/workspace:ro" \
|
||||||
-e HOME=/root \
|
-e HOME=/root \
|
||||||
-e UNCAGED_WORKFLOW_STORAGE_ROOT=/tmp/uwf-e2e-storage \
|
-e UWF_STORAGE_ROOT=/tmp/uwf-e2e-storage \
|
||||||
--add-host=host.docker.internal:host-gateway \
|
--add-host=host.docker.internal:host-gateway \
|
||||||
-w /workspace \
|
-w /workspace \
|
||||||
node:22-bookworm \
|
node:22-bookworm \
|
||||||
@@ -39,7 +39,7 @@ roles:
|
|||||||
export PATH="$HOME/.bun/bin:$PATH"
|
export PATH="$HOME/.bun/bin:$PATH"
|
||||||
|
|
||||||
# Isolated storage
|
# Isolated storage
|
||||||
mkdir -p $UNCAGED_WORKFLOW_STORAGE_ROOT
|
mkdir -p $UWF_STORAGE_ROOT
|
||||||
|
|
||||||
# Install workspace deps
|
# Install workspace deps
|
||||||
cd /root/workflow && bun install
|
cd /root/workflow && bun install
|
||||||
@@ -60,11 +60,11 @@ roles:
|
|||||||
The host config contains provider credentials and model settings needed for LLM calls.
|
The host config contains provider credentials and model settings needed for LLM calls.
|
||||||
Also rewrite any localhost URLs to host.docker.internal so the container can reach host services.
|
Also rewrite any localhost URLs to host.docker.internal so the container can reach host services.
|
||||||
```
|
```
|
||||||
docker cp ~/.uncaged/workflow/config.yaml uwf-e2e-$$:/tmp/uwf-e2e-storage/config.yaml 2>/dev/null || true
|
docker cp ~/.uwf/config.yaml uwf-e2e-$$:/tmp/uwf-e2e-storage/config.yaml 2>/dev/null || true
|
||||||
docker exec uwf-e2e-$$ bash -c '
|
docker exec uwf-e2e-$$ bash -c '
|
||||||
if [ -f $UNCAGED_WORKFLOW_STORAGE_ROOT/config.yaml ]; then
|
if [ -f $UWF_STORAGE_ROOT/config.yaml ]; then
|
||||||
sed -i "s|localhost|host.docker.internal|g; s|127\.0\.0\.1|host.docker.internal|g" \
|
sed -i "s|localhost|host.docker.internal|g; s|127\.0\.0\.1|host.docker.internal|g" \
|
||||||
$UNCAGED_WORKFLOW_STORAGE_ROOT/config.yaml
|
$UWF_STORAGE_ROOT/config.yaml
|
||||||
fi
|
fi
|
||||||
'
|
'
|
||||||
```
|
```
|
||||||
@@ -95,7 +95,7 @@ roles:
|
|||||||
All commands use `uwf` (installed via `bun link` inside the container).
|
All commands use `uwf` (installed via `bun link` inside the container).
|
||||||
Remember to set env vars in each exec:
|
Remember to set env vars in each exec:
|
||||||
export PATH="$HOME/.bun/bin:$PATH"
|
export PATH="$HOME/.bun/bin:$PATH"
|
||||||
export UNCAGED_WORKFLOW_STORAGE_ROOT=/tmp/uwf-e2e-storage
|
export UWF_STORAGE_ROOT=/tmp/uwf-e2e-storage
|
||||||
|
|
||||||
Config tests:
|
Config tests:
|
||||||
1. `uwf config list` — verify it returns valid JSON
|
1. `uwf config list` — verify it returns valid JSON
|
||||||
@@ -133,7 +133,7 @@ roles:
|
|||||||
procedure: |
|
procedure: |
|
||||||
Use the container (containerName) and workflow (workflowName) from your prompt.
|
Use the container (containerName) and workflow (workflowName) from your prompt.
|
||||||
All commands via: `docker exec <containerName> bash -c '...'`
|
All commands via: `docker exec <containerName> bash -c '...'`
|
||||||
Set env: PATH="$HOME/.bun/bin:$PATH" UNCAGED_WORKFLOW_STORAGE_ROOT=/tmp/uwf-e2e-storage
|
Set env: PATH="$HOME/.bun/bin:$PATH" UWF_STORAGE_ROOT=/tmp/uwf-e2e-storage
|
||||||
|
|
||||||
1. `uwf thread start <workflowName> -p 'E2E test: what is 2+2?'` — capture thread ID from JSON output
|
1. `uwf thread start <workflowName> -p 'E2E test: what is 2+2?'` — capture thread ID from JSON output
|
||||||
2. `uwf thread list` — verify the thread appears in the list
|
2. `uwf thread list` — verify the thread appears in the list
|
||||||
@@ -166,7 +166,7 @@ roles:
|
|||||||
procedure: |
|
procedure: |
|
||||||
Use the container (containerName) and threadId from your prompt.
|
Use the container (containerName) and threadId from your prompt.
|
||||||
All commands via: `docker exec <containerName> bash -c '...'`
|
All commands via: `docker exec <containerName> bash -c '...'`
|
||||||
Set env: PATH="$HOME/.bun/bin:$PATH" UNCAGED_WORKFLOW_STORAGE_ROOT=/tmp/uwf-e2e-storage
|
Set env: PATH="$HOME/.bun/bin:$PATH" UWF_STORAGE_ROOT=/tmp/uwf-e2e-storage
|
||||||
|
|
||||||
Step inspection:
|
Step inspection:
|
||||||
1. `uwf step list <threadId>` — verify steps array has length > 1
|
1. `uwf step list <threadId>` — verify steps array has length > 1
|
||||||
@@ -208,7 +208,7 @@ roles:
|
|||||||
procedure: |
|
procedure: |
|
||||||
Use containerName, threadId, lastStepHash, and workflowName from your prompt.
|
Use containerName, threadId, lastStepHash, and workflowName from your prompt.
|
||||||
All commands via: `docker exec <containerName> bash -c '...'`
|
All commands via: `docker exec <containerName> bash -c '...'`
|
||||||
Set env: PATH="$HOME/.bun/bin:$PATH" UNCAGED_WORKFLOW_STORAGE_ROOT=/tmp/uwf-e2e-storage
|
Set env: PATH="$HOME/.bun/bin:$PATH" UWF_STORAGE_ROOT=/tmp/uwf-e2e-storage
|
||||||
|
|
||||||
Cancel:
|
Cancel:
|
||||||
1. Start a second thread: `uwf thread start <workflowName> -p 'E2E cancel test'`
|
1. Start a second thread: `uwf thread start <workflowName> -p 'E2E cancel test'`
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ roles:
|
|||||||
"https://git.shazhou.work/api/v1/repos/<owner>/<repo>/pulls" \
|
"https://git.shazhou.work/api/v1/repos/<owner>/<repo>/pulls" \
|
||||||
-d '{"title":"...","body":"...","head":"<branch>","base":"main"}'
|
-d '{"title":"...","body":"...","head":"<branch>","base":"main"}'
|
||||||
```
|
```
|
||||||
- The repo remote (owner/repo format, e.g. "uncaged/workflow") is given in your task prompt — use it directly.
|
- The repo remote (owner/repo format, e.g. "shazhou/united-workforce") is given in your task prompt — use it directly.
|
||||||
- PR body must include: What / Why / Changes / Ref sections, with `Fixes #N` in Ref
|
- PR body must include: What / Why / Changes / Ref sections, with `Fixes #N` in Ref
|
||||||
6. **Verify PR was created** — parse the curl response JSON: it must contain a `"number"` field. Print the PR URL.
|
6. **Verify PR was created** — parse the curl response JSON: it must contain a `"number"` field. Print the PR URL.
|
||||||
- If curl returns an error or no number field: capture the response, mark hook_failed
|
- If curl returns an error or no number field: capture the response, mark hook_failed
|
||||||
|
|||||||
+12
-12
@@ -1,6 +1,6 @@
|
|||||||
# UWF Bootstrap Guide
|
# UWF Bootstrap Guide
|
||||||
|
|
||||||
This guide helps any AI agent set up `uwf` (Uncaged Workflow) from scratch — or self-check and upgrade an existing installation.
|
This guide helps any AI agent set up `uwf` (United Workforce) from scratch — or self-check and upgrade an existing installation.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ This guide helps any AI agent set up `uwf` (Uncaged Workflow) from scratch — o
|
|||||||
### 1. Install uwf CLI
|
### 1. Install uwf CLI
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun install -g @uncaged/cli
|
bun install -g @united-workforce/cli
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ **Check:** `uwf --version` prints a version number (e.g. `0.5.1`).
|
✅ **Check:** `uwf --version` prints a version number (e.g. `0.5.1`).
|
||||||
@@ -27,11 +27,11 @@ Install the adapter that matches your agent runtime. Pick **one**:
|
|||||||
|
|
||||||
| Agent | Package | Binary |
|
| Agent | Package | Binary |
|
||||||
|-------|---------|--------|
|
|-------|---------|--------|
|
||||||
| Hermes | `@uncaged/agent-hermes` | `uwf-hermes` |
|
| Hermes | `@united-workforce/agent-hermes` | `uwf-hermes` |
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Example: Hermes agent
|
# Example: Hermes agent
|
||||||
bun install -g @uncaged/agent-hermes
|
bun install -g @united-workforce/agent-hermes
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ **Check:** `uwf-hermes --version` prints a version number.
|
✅ **Check:** `uwf-hermes --version` prints a version number.
|
||||||
@@ -55,7 +55,7 @@ uwf setup \
|
|||||||
--agent hermes
|
--agent hermes
|
||||||
```
|
```
|
||||||
|
|
||||||
This creates `~/.uncaged/workflow/config.yaml` with your provider, model, and default agent.
|
This creates `~/.uwf/config.yaml` with your provider, model, and default agent.
|
||||||
|
|
||||||
#### Config Structure
|
#### Config Structure
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ defaultAgent: hermes
|
|||||||
defaultModel: default
|
defaultModel: default
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ **Check:** `cat ~/.uncaged/workflow/config.yaml` shows valid provider, model, and agent config.
|
✅ **Check:** `cat ~/.uwf/config.yaml` shows valid provider, model, and agent config.
|
||||||
|
|
||||||
### 4. Verify Installation
|
### 4. Verify Installation
|
||||||
|
|
||||||
@@ -129,23 +129,23 @@ Compare with latest published versions:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun pm ls -g | grep -E "cli|workflow-agent"
|
bun pm ls -g | grep -E "cli|workflow-agent"
|
||||||
npm info @uncaged/cli version
|
npm info @united-workforce/cli version
|
||||||
npm info @uncaged/agent-hermes version
|
npm info @united-workforce/agent-hermes version
|
||||||
```
|
```
|
||||||
|
|
||||||
If local version < published version, upgrade:
|
If local version < published version, upgrade:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun install -g @uncaged/cli@latest
|
bun install -g @united-workforce/cli@latest
|
||||||
bun install -g @uncaged/agent-hermes@latest
|
bun install -g @united-workforce/agent-hermes@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ **Check:** `uwf --version` matches `npm info @uncaged/cli version`.
|
✅ **Check:** `uwf --version` matches `npm info @united-workforce/cli version`.
|
||||||
|
|
||||||
### Config Check
|
### Config Check
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cat ~/.uncaged/workflow/config.yaml
|
cat ~/.uwf/config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
Verify:
|
Verify:
|
||||||
|
|||||||
+4
-4
@@ -1,4 +1,4 @@
|
|||||||
# Contributing to @uncaged/workflow
|
# Contributing to @united-workforce/cli
|
||||||
|
|
||||||
Thank you for your interest in contributing! This guide covers setup, conventions, and the PR workflow.
|
Thank you for your interest in contributing! This guide covers setup, conventions, and the PR workflow.
|
||||||
|
|
||||||
@@ -11,8 +11,8 @@ Thank you for your interest in contributing! This guide covers setup, convention
|
|||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/shazhou-ww/uncaged-workflow.git
|
git clone https://github.com/shazhou-ww/united-workforce.git
|
||||||
cd uncaged-workflow
|
cd united-workforce
|
||||||
bun install
|
bun install
|
||||||
bun run build
|
bun run build
|
||||||
bun test
|
bun test
|
||||||
@@ -36,7 +36,7 @@ See [CLAUDE.md](CLAUDE.md) for the full coding standard. Key points:
|
|||||||
- **Functional-first** — `function` + `type`, not `class` + `interface`
|
- **Functional-first** — `function` + `type`, not `class` + `interface`
|
||||||
- **No optional properties** — use `T | null` instead of `?:`
|
- **No optional properties** — use `T | null` instead of `?:`
|
||||||
- **Named exports only** — no default exports
|
- **Named exports only** — no default exports
|
||||||
- **No `console.log`** — use the structured logger from `@uncaged/util`
|
- **No `console.log`** — use the structured logger from `@united-workforce/util`
|
||||||
- **Static imports only** — no `await import()` in production code
|
- **Static imports only** — no `await import()` in production code
|
||||||
- **Biome** for lint + format — run `bun run check` before committing
|
- **Biome** for lint + format — run `bun run check` before committing
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2026 Uncaged
|
Copyright (c) 2026 United Workforce
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# United Workforce (uwf)
|
# United Workforce (uwf)
|
||||||
|
|
||||||
[](https://github.com/shazhou-ww/uncaged-workflow/actions/workflows/ci.yml)
|
[](https://github.com/shazhou-ww/united-workforce/actions/workflows/ci.yml)
|
||||||
[](https://www.npmjs.com/package/@united-workforce/cli)
|
[](https://www.npmjs.com/package/@united-workforce/cli)
|
||||||
[](https://www.npmjs.com/package/@united-workforce/protocol)
|
[](https://www.npmjs.com/package/@united-workforce/protocol)
|
||||||
[](https://www.npmjs.com/package/@united-workforce/util-agent)
|
[](https://www.npmjs.com/package/@united-workforce/util-agent)
|
||||||
|
|||||||
@@ -358,7 +358,7 @@ threads.yaml: { "01J7K9...4T": "8FWKR3TN5V1QA" }
|
|||||||
## Storage layout
|
## Storage layout
|
||||||
|
|
||||||
```
|
```
|
||||||
~/.uncaged/workflow/
|
~/.uwf/
|
||||||
├── cas/ # json-cas filesystem store (all CAS nodes)
|
├── cas/ # json-cas filesystem store (all CAS nodes)
|
||||||
├── config.yaml # Provider, model, agent configuration
|
├── config.yaml # Provider, model, agent configuration
|
||||||
├── threads.yaml # Active thread head pointers: threadId → CasRef
|
├── threads.yaml # Active thread head pointers: threadId → CasRef
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ Agent 解析优先级(`resolveAgentConfig`):
|
|||||||
|
|
||||||
```33:43:packages/util-agent/src/storage.ts
|
```33:43:packages/util-agent/src/storage.ts
|
||||||
export function resolveStorageRoot(): string {
|
export function resolveStorageRoot(): string {
|
||||||
const internal = process.env.UNCAGED_WORKFLOW_STORAGE_ROOT;
|
const internal = process.env.UWF_STORAGE_ROOT;
|
||||||
if (internal !== undefined && internal !== "") {
|
if (internal !== undefined && internal !== "") {
|
||||||
return internal;
|
return internal;
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ export function resolveStorageRoot(): string {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Agent 子进程通过继承的 `process.env` 与父 CLI 共享同一 storage root;`createAgent` 内还会 `loadDotenv({ path: getEnvPath(storageRoot) })` 加载 `~/.uncaged/workflow/.env`。
|
Agent 子进程通过继承的 `process.env` 与父 CLI 共享同一 storage root;`createAgent` 内还会 `loadDotenv({ path: getEnvPath(storageRoot) })` 加载 `~/.uwf/.env`。
|
||||||
|
|
||||||
#### Agent 侧职责(设计文档 + 实现)
|
#### Agent 侧职责(设计文档 + 实现)
|
||||||
|
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ payload:
|
|||||||
- `graph` — `Record<Role | "$START", Record<Status, Target>>`,每个 Target = `{ role, prompt }`
|
- `graph` — `Record<Role | "$START", Record<Status, Target>>`,每个 Target = `{ role, prompt }`
|
||||||
- Status 来自上一个 role 输出的 `status` 字段,`$START` 用 `_` 作为初始 status
|
- Status 来自上一个 role 输出的 `status` 字段,`$START` 用 `_` 作为初始 status
|
||||||
- Prompt 模板使用 Mustache 渲染,变量来自 lastOutput
|
- Prompt 模板使用 Mustache 渲染,变量来自 lastOutput
|
||||||
- 不含 agent binding — agent 配置在 `~/.uncaged/workflow/config.yaml` 中管理
|
- 不含 agent binding — agent 配置在 `~/.uwf/config.yaml` 中管理
|
||||||
|
|
||||||
Moderator 的求值逻辑:
|
Moderator 的求值逻辑:
|
||||||
|
|
||||||
@@ -276,7 +276,7 @@ threads.yaml: { "01J7K9M2XNPQR5VWBCDF8G3H4T": "8FWKR3TN5V1QA" }
|
|||||||
系统两个顶层 YAML 文件和一个 env 文件:
|
系统两个顶层 YAML 文件和一个 env 文件:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# ~/.uncaged/workflow/config.yaml — 全局配置
|
# ~/.uwf/config.yaml — 全局配置
|
||||||
providers:
|
providers:
|
||||||
openai:
|
openai:
|
||||||
baseUrl: "https://api.openai.com/v1"
|
baseUrl: "https://api.openai.com/v1"
|
||||||
@@ -315,7 +315,7 @@ modelOverrides:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# ~/.uncaged/workflow/threads.yaml — active thread 链头指针
|
# ~/.uwf/threads.yaml — active thread 链头指针
|
||||||
01J7K9M2XNPQR5VWBCDF8G3H4T: "8FWKR3TN5V1QA"
|
01J7K9M2XNPQR5VWBCDF8G3H4T: "8FWKR3TN5V1QA"
|
||||||
01J8AB3QRMSTV6WKXZ2C4DF7GN: "3CNWT9KR6D2HV"
|
01J8AB3QRMSTV6WKXZ2C4DF7GN: "3CNWT9KR6D2HV"
|
||||||
```
|
```
|
||||||
@@ -323,7 +323,7 @@ modelOverrides:
|
|||||||
Thread 结束时从 threads.yaml 移除。可选:追加到 `history.jsonl` 做归档。
|
Thread 结束时从 threads.yaml 移除。可选:追加到 `history.jsonl` 做归档。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# ~/.uncaged/workflow/.env — 敏感信息(API keys)
|
# ~/.uwf/.env — 敏感信息(API keys)
|
||||||
OPENAI_API_KEY=sk-...
|
OPENAI_API_KEY=sk-...
|
||||||
ANTHROPIC_API_KEY=sk-ant-...
|
ANTHROPIC_API_KEY=sk-ant-...
|
||||||
OPENROUTER_API_KEY=sk-or-...
|
OPENROUTER_API_KEY=sk-or-...
|
||||||
@@ -478,7 +478,7 @@ type AgentConfig = {
|
|||||||
args: string[];
|
args: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/** ~/.uncaged/workflow/config.yaml */
|
/** ~/.uwf/config.yaml */
|
||||||
type WorkflowConfig = {
|
type WorkflowConfig = {
|
||||||
providers: Record<ProviderAlias, ProviderConfig>;
|
providers: Record<ProviderAlias, ProviderConfig>;
|
||||||
models: Record<ModelAlias, ModelConfig>;
|
models: Record<ModelAlias, ModelConfig>;
|
||||||
@@ -489,7 +489,7 @@ type WorkflowConfig = {
|
|||||||
modelOverrides: Record<Scenario, ModelAlias> | null;
|
modelOverrides: Record<Scenario, ModelAlias> | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** ~/.uncaged/workflow/threads.yaml */
|
/** ~/.uwf/threads.yaml */
|
||||||
type ThreadsIndex = Record<ThreadId, CasRef>;
|
type ThreadsIndex = Record<ThreadId, CasRef>;
|
||||||
// ^ thread-id ^ head StepNode/StartNode hash
|
// ^ thread-id ^ head StepNode/StartNode hash
|
||||||
```
|
```
|
||||||
|
|||||||
+3
-3
@@ -32,11 +32,11 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/shazhou-ww/uncaged-workflow.git"
|
"url": "https://github.com/shazhou-ww/united-workforce.git"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme",
|
"homepage": "https://github.com/shazhou-ww/united-workforce#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/shazhou-ww/uncaged-workflow/issues"
|
"url": "https://github.com/shazhou-ww/united-workforce/issues"
|
||||||
},
|
},
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,6 @@ src/
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Requires a configured OpenAI-compatible provider and model in `~/.uncaged/workflow/config.yaml` (via `uwf setup`). API keys are loaded from `~/.uncaged/workflow/.env`.
|
Requires a configured OpenAI-compatible provider and model in `~/.uwf/config.yaml` (via `uwf setup`). API keys are loaded from `~/.uwf/.env`.
|
||||||
|
|
||||||
Tools run with the current working directory as `ToolContext.cwd` (typically the directory where `uwf thread step` was invoked).
|
Tools run with the current working directory as `ToolContext.cwd` (typically the directory where `uwf thread step` was invoked).
|
||||||
|
|||||||
@@ -35,12 +35,12 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.shazhou.work/uncaged/workflow.git",
|
"url": "https://git.shazhou.work/shazhou/united-workforce.git",
|
||||||
"directory": "packages/agent-builtin"
|
"directory": "packages/agent-builtin"
|
||||||
},
|
},
|
||||||
"homepage": "https://git.shazhou.work/uncaged/workflow#readme",
|
"homepage": "https://git.shazhou.work/shazhou/united-workforce#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://git.shazhou.work/uncaged/workflow/issues"
|
"url": "https://git.shazhou.work/shazhou/united-workforce/issues"
|
||||||
},
|
},
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,12 +35,12 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.shazhou.work/uncaged/workflow.git",
|
"url": "https://git.shazhou.work/shazhou/united-workforce.git",
|
||||||
"directory": "packages/agent-claude-code"
|
"directory": "packages/agent-claude-code"
|
||||||
},
|
},
|
||||||
"homepage": "https://git.shazhou.work/uncaged/workflow#readme",
|
"homepage": "https://git.shazhou.work/shazhou/united-workforce#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://git.shazhou.work/uncaged/workflow/issues"
|
"url": "https://git.shazhou.work/shazhou/united-workforce/issues"
|
||||||
},
|
},
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,6 @@ src/
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Uses workflow config from `~/.uncaged/workflow/config.yaml` (via agent-kit). Hermes session files are stored under the workflow storage root (see `session-detail.ts`).
|
Uses workflow config from `~/.uwf/config.yaml` (via agent-kit). Hermes session files are stored under the workflow storage root (see `session-detail.ts`).
|
||||||
|
|
||||||
Set `UWF_HERMES_NO_RESUME=1` to disable session resume (see `isResumeDisabled` in `session-cache.ts`).
|
Set `UWF_HERMES_NO_RESUME=1` to disable session resume (see `isResumeDisabled` in `session-cache.ts`).
|
||||||
|
|||||||
@@ -36,12 +36,12 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.shazhou.work/uncaged/workflow.git",
|
"url": "https://git.shazhou.work/shazhou/united-workforce.git",
|
||||||
"directory": "packages/agent-hermes"
|
"directory": "packages/agent-hermes"
|
||||||
},
|
},
|
||||||
"homepage": "https://git.shazhou.work/uncaged/workflow#readme",
|
"homepage": "https://git.shazhou.work/shazhou/united-workforce#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://git.shazhou.work/uncaged/workflow/issues"
|
"url": "https://git.shazhou.work/shazhou/united-workforce/issues"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"bun": ">= 1.0.0"
|
"bun": ">= 1.0.0"
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ uwf setup --provider openai --base-url https://api.openai.com/v1 \
|
|||||||
--api-key sk-... --model gpt-4o --agent hermes
|
--api-key sk-... --model gpt-4o --agent hermes
|
||||||
```
|
```
|
||||||
|
|
||||||
Config: `~/.uncaged/workflow/config.yaml` (includes API keys).
|
Config: `~/.uwf/config.yaml` (includes API keys).
|
||||||
|
|
||||||
### Skill
|
### Skill
|
||||||
|
|
||||||
@@ -206,17 +206,17 @@ src/
|
|||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| `~/.uncaged/workflow/config.yaml` | Providers, models, default agent |
|
| `~/.uwf/config.yaml` | Providers, models, default agent |
|
||||||
| `~/.uncaged/workflow/.env` | API keys (referenced by `apiKeyEnv` in config) |
|
| `~/.uwf/.env` | API keys (referenced by `apiKeyEnv` in config) |
|
||||||
| `~/.uncaged/workflow/registry.yaml` | Workflow name → CAS hash |
|
| `~/.uwf/registry.yaml` | Workflow name → CAS hash |
|
||||||
| `~/.uncaged/workflow/threads.yaml` | Active thread head pointers |
|
| `~/.uwf/threads.yaml` | Active thread head pointers |
|
||||||
| `~/.uncaged/json-cas/` | Content-addressed node storage (unified CAS store, shared with `ocas` CLI) |
|
| `~/.ocas/` | Content-addressed node storage (unified CAS store, shared with `ocas` CLI) |
|
||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|
||||||
| Variable | Purpose | Default |
|
| Variable | Purpose | Default |
|
||||||
|----------|---------|---------|
|
|----------|---------|---------|
|
||||||
| `UNCAGED_CAS_DIR` | Override the global CAS directory location | `~/.uncaged/json-cas` |
|
| `OCAS_DIR` | Override the global CAS directory location | `~/.ocas` |
|
||||||
| `UNCAGED_WORKFLOW_STORAGE_ROOT` | Internal override for workflow metadata storage | `~/.uncaged/workflow` |
|
| `UWF_STORAGE_ROOT` | Internal override for workflow metadata storage | `~/.uwf` |
|
||||||
| `WORKFLOW_STORAGE_ROOT` | User override for workflow metadata storage | `~/.uncaged/workflow` |
|
| `WORKFLOW_STORAGE_ROOT` | User override for workflow metadata storage | `~/.uwf` |
|
||||||
|
|
||||||
|
|||||||
@@ -34,12 +34,12 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.shazhou.work/uncaged/workflow.git",
|
"url": "https://git.shazhou.work/shazhou/united-workforce.git",
|
||||||
"directory": "packages/cli"
|
"directory": "packages/cli"
|
||||||
},
|
},
|
||||||
"homepage": "https://git.shazhou.work/uncaged/workflow#readme",
|
"homepage": "https://git.shazhou.work/shazhou/united-workforce#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://git.shazhou.work/uncaged/workflow/issues"
|
"url": "https://git.shazhou.work/shazhou/united-workforce/issues"
|
||||||
},
|
},
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ describe("C1: adapter JSON round-trip integration", () => {
|
|||||||
prompt: "Test round-trip task",
|
prompt: "Test round-trip task",
|
||||||
});
|
});
|
||||||
|
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
|
|
||||||
const threadId = "01ROUNDTRIPTEST0000000000" as ThreadId;
|
const threadId = "01ROUNDTRIPTEST0000000000" as ThreadId;
|
||||||
await seedThreads(tmpDir, { [threadId]: startHash });
|
await seedThreads(tmpDir, { [threadId]: startHash });
|
||||||
@@ -134,7 +134,7 @@ describe("C1: adapter JSON round-trip integration", () => {
|
|||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
WORKFLOW_STORAGE_ROOT: tmpDir,
|
WORKFLOW_STORAGE_ROOT: tmpDir,
|
||||||
UNCAGED_CAS_DIR: casDir,
|
OCAS_DIR: casDir,
|
||||||
},
|
},
|
||||||
cwd: tmpDir,
|
cwd: tmpDir,
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
|
|||||||
@@ -225,9 +225,9 @@ describe("currentRole field", () => {
|
|||||||
await mkdir(storageRoot, { recursive: true });
|
await mkdir(storageRoot, { recursive: true });
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
|
|
||||||
// Set UNCAGED_CAS_DIR for this test
|
// Set OCAS_DIR for this test
|
||||||
originalEnv = process.env.UNCAGED_CAS_DIR;
|
originalEnv = process.env.OCAS_DIR;
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function teardown() {
|
async function teardown() {
|
||||||
@@ -236,9 +236,9 @@ describe("currentRole field", () => {
|
|||||||
}
|
}
|
||||||
// Restore original environment
|
// Restore original environment
|
||||||
if (originalEnv === undefined) {
|
if (originalEnv === undefined) {
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.UNCAGED_CAS_DIR = originalEnv;
|
process.env.OCAS_DIR = originalEnv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ beforeEach(async () => {
|
|||||||
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-resolve-head-"));
|
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-resolve-head-"));
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
|||||||
@@ -70,16 +70,16 @@ let originalEnv: string | undefined;
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-step-read-test-"));
|
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-step-read-test-"));
|
||||||
originalEnv = process.env.UNCAGED_CAS_DIR;
|
originalEnv = process.env.OCAS_DIR;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await rm(tmpDir, { recursive: true, force: true });
|
await rm(tmpDir, { recursive: true, force: true });
|
||||||
// Restore original environment
|
// Restore original environment
|
||||||
if (originalEnv === undefined) {
|
if (originalEnv === undefined) {
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.UNCAGED_CAS_DIR = originalEnv;
|
process.env.OCAS_DIR = originalEnv;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -88,10 +88,10 @@ afterEach(async () => {
|
|||||||
describe("step read", () => {
|
describe("step read", () => {
|
||||||
test("test 1: basic single-step read with 3 turns", async () => {
|
test("test 1: basic single-step read with 3 turns", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
const detailSchemas = await registerDetailSchemas(store);
|
const detailSchemas = await registerDetailSchemas(store);
|
||||||
@@ -177,9 +177,9 @@ describe("step read", () => {
|
|||||||
|
|
||||||
test("test 2: quota enforcement - multiple turns", async () => {
|
test("test 2: quota enforcement - multiple turns", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
const detailSchemas = await registerDetailSchemas(store);
|
const detailSchemas = await registerDetailSchemas(store);
|
||||||
@@ -263,9 +263,9 @@ describe("step read", () => {
|
|||||||
|
|
||||||
test("test 3: minimal quota edge case - always show at least one turn", async () => {
|
test("test 3: minimal quota edge case - always show at least one turn", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
const detailSchemas = await registerDetailSchemas(store);
|
const detailSchemas = await registerDetailSchemas(store);
|
||||||
@@ -340,9 +340,9 @@ describe("step read", () => {
|
|||||||
|
|
||||||
test("test 4: step with no detail field", async () => {
|
test("test 4: step with no detail field", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
|
|
||||||
@@ -401,9 +401,9 @@ describe("step read", () => {
|
|||||||
|
|
||||||
test("test 5: step with detail but no turns array", async () => {
|
test("test 5: step with detail but no turns array", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
await registerDetailSchemas(store);
|
await registerDetailSchemas(store);
|
||||||
@@ -479,9 +479,9 @@ describe("step read", () => {
|
|||||||
|
|
||||||
test("test 6: displays role and tool calls in turn body", async () => {
|
test("test 6: displays role and tool calls in turn body", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
const detailSchemas = await registerDetailSchemas(store);
|
const detailSchemas = await registerDetailSchemas(store);
|
||||||
@@ -553,9 +553,9 @@ describe("step read", () => {
|
|||||||
|
|
||||||
test("test 7: turn content with special characters", async () => {
|
test("test 7: turn content with special characters", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
const schemas = await registerUwfSchemas(store);
|
const schemas = await registerUwfSchemas(store);
|
||||||
const detailSchemas = await registerDetailSchemas(store);
|
const detailSchemas = await registerDetailSchemas(store);
|
||||||
|
|||||||
@@ -131,16 +131,16 @@ describe("cmdStepShow JSON serialization", () => {
|
|||||||
testDir = await mkdtemp(join(tmpdir(), "uwf-test-"));
|
testDir = await mkdtemp(join(tmpdir(), "uwf-test-"));
|
||||||
casDir = join(testDir, "cas");
|
casDir = join(testDir, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
originalEnv = process.env.UNCAGED_CAS_DIR;
|
originalEnv = process.env.OCAS_DIR;
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await rm(testDir, { recursive: true, force: true });
|
await rm(testDir, { recursive: true, force: true });
|
||||||
if (originalEnv === undefined) {
|
if (originalEnv === undefined) {
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.UNCAGED_CAS_DIR = originalEnv;
|
process.env.OCAS_DIR = originalEnv;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -67,17 +67,17 @@ let originalEnv: string | undefined;
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-step-timing-test-"));
|
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-step-timing-test-"));
|
||||||
originalEnv = process.env.UNCAGED_CAS_DIR;
|
originalEnv = process.env.OCAS_DIR;
|
||||||
process.env.UNCAGED_CAS_DIR = join(tmpDir, "cas");
|
process.env.OCAS_DIR = join(tmpDir, "cas");
|
||||||
await mkdir(process.env.UNCAGED_CAS_DIR, { recursive: true });
|
await mkdir(process.env.OCAS_DIR, { recursive: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await rm(tmpDir, { recursive: true, force: true });
|
await rm(tmpDir, { recursive: true, force: true });
|
||||||
if (originalEnv === undefined) {
|
if (originalEnv === undefined) {
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.UNCAGED_CAS_DIR = originalEnv;
|
process.env.OCAS_DIR = originalEnv;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -16,13 +16,11 @@ import {
|
|||||||
describe("Global CAS directory", () => {
|
describe("Global CAS directory", () => {
|
||||||
let tmpDir: string;
|
let tmpDir: string;
|
||||||
let originalOcasDir: string | undefined;
|
let originalOcasDir: string | undefined;
|
||||||
let originalLegacyCasDir: string | undefined;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
tmpDir = join(tmpdir(), `uwf-test-global-cas-${Date.now()}`);
|
tmpDir = join(tmpdir(), `uwf-test-global-cas-${Date.now()}`);
|
||||||
await mkdir(tmpDir, { recursive: true });
|
await mkdir(tmpDir, { recursive: true });
|
||||||
originalOcasDir = process.env.OCAS_DIR;
|
originalOcasDir = process.env.OCAS_DIR;
|
||||||
originalLegacyCasDir = process.env.UNCAGED_CAS_DIR;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -34,16 +32,10 @@ describe("Global CAS directory", () => {
|
|||||||
} else {
|
} else {
|
||||||
process.env.OCAS_DIR = originalOcasDir;
|
process.env.OCAS_DIR = originalOcasDir;
|
||||||
}
|
}
|
||||||
if (originalLegacyCasDir === undefined) {
|
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
|
||||||
} else {
|
|
||||||
process.env.UNCAGED_CAS_DIR = originalLegacyCasDir;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("getGlobalCasDir returns default path when no env var set", () => {
|
test("getGlobalCasDir returns default path when no env var set", () => {
|
||||||
delete process.env.OCAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
|
||||||
const casDir = getGlobalCasDir();
|
const casDir = getGlobalCasDir();
|
||||||
expect(casDir).toContain(".ocas");
|
expect(casDir).toContain(".ocas");
|
||||||
});
|
});
|
||||||
@@ -55,22 +47,8 @@ describe("Global CAS directory", () => {
|
|||||||
expect(casDir).toBe(customPath);
|
expect(casDir).toBe(customPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("getGlobalCasDir respects UNCAGED_CAS_DIR environment variable", () => {
|
|
||||||
const customPath = join(tmpDir, "legacy-cas");
|
|
||||||
process.env.UNCAGED_CAS_DIR = customPath;
|
|
||||||
const casDir = getGlobalCasDir();
|
|
||||||
expect(casDir).toBe(customPath);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("getGlobalCasDir prefers OCAS_DIR over UNCAGED_CAS_DIR", () => {
|
|
||||||
process.env.OCAS_DIR = join(tmpDir, "primary-cas");
|
|
||||||
process.env.UNCAGED_CAS_DIR = join(tmpDir, "legacy-cas");
|
|
||||||
expect(getGlobalCasDir()).toBe(join(tmpDir, "primary-cas"));
|
|
||||||
});
|
|
||||||
|
|
||||||
test("getGlobalCasDir ignores empty OCAS_DIR", () => {
|
test("getGlobalCasDir ignores empty OCAS_DIR", () => {
|
||||||
process.env.OCAS_DIR = "";
|
process.env.OCAS_DIR = "";
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
|
||||||
const casDir = getGlobalCasDir();
|
const casDir = getGlobalCasDir();
|
||||||
expect(casDir).toContain(".ocas");
|
expect(casDir).toContain(".ocas");
|
||||||
});
|
});
|
||||||
@@ -83,7 +61,7 @@ describe("Global CAS directory", () => {
|
|||||||
|
|
||||||
test("createUwfStore uses global CAS directory", async () => {
|
test("createUwfStore uses global CAS directory", async () => {
|
||||||
const globalCasDir = join(tmpDir, "global-cas");
|
const globalCasDir = join(tmpDir, "global-cas");
|
||||||
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
process.env.OCAS_DIR = globalCasDir;
|
||||||
|
|
||||||
const storageRoot = join(tmpDir, "storage");
|
const storageRoot = join(tmpDir, "storage");
|
||||||
await mkdir(storageRoot, { recursive: true });
|
await mkdir(storageRoot, { recursive: true });
|
||||||
@@ -104,7 +82,7 @@ describe("Global CAS directory", () => {
|
|||||||
|
|
||||||
test("createUwfStore creates global CAS directory if it does not exist", async () => {
|
test("createUwfStore creates global CAS directory if it does not exist", async () => {
|
||||||
const globalCasDir = join(tmpDir, "new-global-cas");
|
const globalCasDir = join(tmpDir, "new-global-cas");
|
||||||
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
process.env.OCAS_DIR = globalCasDir;
|
||||||
|
|
||||||
const storageRoot = join(tmpDir, "storage");
|
const storageRoot = join(tmpDir, "storage");
|
||||||
await mkdir(storageRoot, { recursive: true });
|
await mkdir(storageRoot, { recursive: true });
|
||||||
@@ -119,7 +97,7 @@ describe("Global CAS directory", () => {
|
|||||||
|
|
||||||
test("multiple uwfStore instances share the same global CAS filesystem", async () => {
|
test("multiple uwfStore instances share the same global CAS filesystem", async () => {
|
||||||
const globalCasDir = join(tmpDir, "shared-cas");
|
const globalCasDir = join(tmpDir, "shared-cas");
|
||||||
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
process.env.OCAS_DIR = globalCasDir;
|
||||||
|
|
||||||
const storageRoot1 = join(tmpDir, "storage1");
|
const storageRoot1 = join(tmpDir, "storage1");
|
||||||
const storageRoot2 = join(tmpDir, "storage2");
|
const storageRoot2 = join(tmpDir, "storage2");
|
||||||
@@ -149,7 +127,7 @@ describe("Global CAS directory", () => {
|
|||||||
|
|
||||||
test("workflow registry is stored in global CAS variable store", async () => {
|
test("workflow registry is stored in global CAS variable store", async () => {
|
||||||
const globalCasDir = join(tmpDir, "global-cas");
|
const globalCasDir = join(tmpDir, "global-cas");
|
||||||
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
process.env.OCAS_DIR = globalCasDir;
|
||||||
|
|
||||||
const storageRoot = join(tmpDir, "storage");
|
const storageRoot = join(tmpDir, "storage");
|
||||||
await mkdir(storageRoot, { recursive: true });
|
await mkdir(storageRoot, { recursive: true });
|
||||||
@@ -170,7 +148,7 @@ describe("Global CAS directory", () => {
|
|||||||
|
|
||||||
test("migrates workflows.yaml to variable store and renames file", async () => {
|
test("migrates workflows.yaml to variable store and renames file", async () => {
|
||||||
const globalCasDir = join(tmpDir, "global-cas");
|
const globalCasDir = join(tmpDir, "global-cas");
|
||||||
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
process.env.OCAS_DIR = globalCasDir;
|
||||||
|
|
||||||
const storageRoot = join(tmpDir, "storage-migrate");
|
const storageRoot = join(tmpDir, "storage-migrate");
|
||||||
await mkdir(storageRoot, { recursive: true });
|
await mkdir(storageRoot, { recursive: true });
|
||||||
@@ -195,7 +173,7 @@ describe("Global CAS directory", () => {
|
|||||||
|
|
||||||
test("migrates threads.yaml to variable store and renames file", async () => {
|
test("migrates threads.yaml to variable store and renames file", async () => {
|
||||||
const globalCasDir = join(tmpDir, "global-cas-threads");
|
const globalCasDir = join(tmpDir, "global-cas-threads");
|
||||||
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
process.env.OCAS_DIR = globalCasDir;
|
||||||
|
|
||||||
const storageRoot = join(tmpDir, "storage-threads-migrate");
|
const storageRoot = join(tmpDir, "storage-threads-migrate");
|
||||||
await mkdir(storageRoot, { recursive: true });
|
await mkdir(storageRoot, { recursive: true });
|
||||||
@@ -219,7 +197,7 @@ describe("Global CAS directory", () => {
|
|||||||
|
|
||||||
test("thread metadata stored in ocas variable store", async () => {
|
test("thread metadata stored in ocas variable store", async () => {
|
||||||
const globalCasDir = join(tmpDir, "global-cas");
|
const globalCasDir = join(tmpDir, "global-cas");
|
||||||
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
process.env.OCAS_DIR = globalCasDir;
|
||||||
|
|
||||||
const storageRoot = join(tmpDir, "storage");
|
const storageRoot = join(tmpDir, "storage");
|
||||||
await mkdir(storageRoot, { recursive: true });
|
await mkdir(storageRoot, { recursive: true });
|
||||||
@@ -240,7 +218,7 @@ describe("Global CAS directory", () => {
|
|||||||
|
|
||||||
test("history is stored in global CAS variable store", async () => {
|
test("history is stored in global CAS variable store", async () => {
|
||||||
const globalCasDir = join(tmpDir, "global-cas");
|
const globalCasDir = join(tmpDir, "global-cas");
|
||||||
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
process.env.OCAS_DIR = globalCasDir;
|
||||||
|
|
||||||
const storageRoot = join(tmpDir, "storage");
|
const storageRoot = join(tmpDir, "storage");
|
||||||
await mkdir(storageRoot, { recursive: true });
|
await mkdir(storageRoot, { recursive: true });
|
||||||
@@ -271,7 +249,7 @@ describe("Global CAS directory", () => {
|
|||||||
|
|
||||||
test("migrates history.jsonl to variable store and renames file", async () => {
|
test("migrates history.jsonl to variable store and renames file", async () => {
|
||||||
const globalCasDir = join(tmpDir, "global-cas-history");
|
const globalCasDir = join(tmpDir, "global-cas-history");
|
||||||
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
process.env.OCAS_DIR = globalCasDir;
|
||||||
|
|
||||||
const storageRoot = join(tmpDir, "storage-history-migrate");
|
const storageRoot = join(tmpDir, "storage-history-migrate");
|
||||||
await mkdir(storageRoot, { recursive: true });
|
await mkdir(storageRoot, { recursive: true });
|
||||||
@@ -314,7 +292,7 @@ describe("Global CAS directory", () => {
|
|||||||
|
|
||||||
test("CAS nodes are stored in global directory", async () => {
|
test("CAS nodes are stored in global directory", async () => {
|
||||||
const globalCasDir = join(tmpDir, "global-cas");
|
const globalCasDir = join(tmpDir, "global-cas");
|
||||||
process.env.UNCAGED_CAS_DIR = globalCasDir;
|
process.env.OCAS_DIR = globalCasDir;
|
||||||
|
|
||||||
const storageRoot = join(tmpDir, "storage");
|
const storageRoot = join(tmpDir, "storage");
|
||||||
await mkdir(storageRoot, { recursive: true });
|
await mkdir(storageRoot, { recursive: true });
|
||||||
|
|||||||
@@ -10,13 +10,7 @@ import {
|
|||||||
} from "../store.js";
|
} from "../store.js";
|
||||||
|
|
||||||
describe("Storage root resolution", () => {
|
describe("Storage root resolution", () => {
|
||||||
const envKeys = [
|
const envKeys = ["UWF_STORAGE_ROOT", "WORKFLOW_STORAGE_ROOT", "OCAS_DIR"] as const;
|
||||||
"UWF_STORAGE_ROOT",
|
|
||||||
"WORKFLOW_STORAGE_ROOT",
|
|
||||||
"UNCAGED_WORKFLOW_STORAGE_ROOT",
|
|
||||||
"OCAS_DIR",
|
|
||||||
"UNCAGED_CAS_DIR",
|
|
||||||
] as const;
|
|
||||||
const savedEnv: Partial<Record<(typeof envKeys)[number], string | undefined>> = {};
|
const savedEnv: Partial<Record<(typeof envKeys)[number], string | undefined>> = {};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -43,36 +37,23 @@ describe("Storage root resolution", () => {
|
|||||||
test("resolveStorageRoot prefers UWF_STORAGE_ROOT", () => {
|
test("resolveStorageRoot prefers UWF_STORAGE_ROOT", () => {
|
||||||
process.env.UWF_STORAGE_ROOT = "/tmp/uwf-primary";
|
process.env.UWF_STORAGE_ROOT = "/tmp/uwf-primary";
|
||||||
process.env.WORKFLOW_STORAGE_ROOT = "/tmp/uwf-fallback";
|
process.env.WORKFLOW_STORAGE_ROOT = "/tmp/uwf-fallback";
|
||||||
process.env.UNCAGED_WORKFLOW_STORAGE_ROOT = "/tmp/uwf-legacy";
|
|
||||||
expect(resolveStorageRoot()).toBe("/tmp/uwf-primary");
|
expect(resolveStorageRoot()).toBe("/tmp/uwf-primary");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("resolveStorageRoot falls back to WORKFLOW_STORAGE_ROOT", () => {
|
test("resolveStorageRoot falls back to WORKFLOW_STORAGE_ROOT", () => {
|
||||||
process.env.WORKFLOW_STORAGE_ROOT = "/tmp/uwf-fallback";
|
process.env.WORKFLOW_STORAGE_ROOT = "/tmp/uwf-fallback";
|
||||||
process.env.UNCAGED_WORKFLOW_STORAGE_ROOT = "/tmp/uwf-legacy";
|
|
||||||
expect(resolveStorageRoot()).toBe("/tmp/uwf-fallback");
|
expect(resolveStorageRoot()).toBe("/tmp/uwf-fallback");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("resolveStorageRoot falls back to UNCAGED_WORKFLOW_STORAGE_ROOT", () => {
|
|
||||||
process.env.UNCAGED_WORKFLOW_STORAGE_ROOT = "/tmp/uwf-legacy";
|
|
||||||
expect(resolveStorageRoot()).toBe("/tmp/uwf-legacy");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("getGlobalCasDir returns ~/.ocas by default", () => {
|
test("getGlobalCasDir returns ~/.ocas by default", () => {
|
||||||
const casDir = getGlobalCasDir();
|
const casDir = getGlobalCasDir();
|
||||||
expect(casDir).toBe(join(homedir(), ".ocas"));
|
expect(casDir).toBe(join(homedir(), ".ocas"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("getGlobalCasDir prefers OCAS_DIR over UNCAGED_CAS_DIR", () => {
|
test("getGlobalCasDir respects OCAS_DIR", () => {
|
||||||
process.env.OCAS_DIR = "/tmp/ocas-primary";
|
process.env.OCAS_DIR = "/tmp/ocas-primary";
|
||||||
process.env.UNCAGED_CAS_DIR = "/tmp/ocas-legacy";
|
|
||||||
expect(getGlobalCasDir()).toBe("/tmp/ocas-primary");
|
expect(getGlobalCasDir()).toBe("/tmp/ocas-primary");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("getGlobalCasDir falls back to UNCAGED_CAS_DIR", () => {
|
|
||||||
process.env.UNCAGED_CAS_DIR = "/tmp/ocas-legacy";
|
|
||||||
expect(getGlobalCasDir()).toBe("/tmp/ocas-legacy");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("migrateStorageIfNeeded", () => {
|
describe("migrateStorageIfNeeded", () => {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { addHistoryEntry, createUwfStore, loadAllHistory } from "../store.js";
|
|||||||
async function makeUwfStore(storageRoot: string) {
|
async function makeUwfStore(storageRoot: string) {
|
||||||
const casDir = join(storageRoot, "cas");
|
const casDir = join(storageRoot, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
return createUwfStore(storageRoot);
|
return createUwfStore(storageRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import {
|
|||||||
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
||||||
const casDir = join(storageRoot, "cas");
|
const casDir = join(storageRoot, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
// Set UNCAGED_CAS_DIR to use the test's CAS directory
|
// Set OCAS_DIR to use the test's CAS directory
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
return createUwfStore(storageRoot);
|
return createUwfStore(storageRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ describe("Thread and edge location integration", () => {
|
|||||||
await mkdir(storageRoot, { recursive: true });
|
await mkdir(storageRoot, { recursive: true });
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
|
|
||||||
// Set UNCAGED_CAS_DIR for this test
|
// Set OCAS_DIR for this test
|
||||||
originalEnv = process.env.UNCAGED_CAS_DIR;
|
originalEnv = process.env.OCAS_DIR;
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function teardown() {
|
async function teardown() {
|
||||||
@@ -30,9 +30,9 @@ describe("Thread and edge location integration", () => {
|
|||||||
}
|
}
|
||||||
// Restore original environment
|
// Restore original environment
|
||||||
if (originalEnv === undefined) {
|
if (originalEnv === undefined) {
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.UNCAGED_CAS_DIR = originalEnv;
|
process.env.OCAS_DIR = originalEnv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,17 +71,17 @@ let originalEnv: string | undefined;
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-quota-test-"));
|
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-quota-test-"));
|
||||||
originalEnv = process.env.UNCAGED_CAS_DIR;
|
originalEnv = process.env.OCAS_DIR;
|
||||||
process.env.UNCAGED_CAS_DIR = join(tmpDir, "cas");
|
process.env.OCAS_DIR = join(tmpDir, "cas");
|
||||||
await mkdir(process.env.UNCAGED_CAS_DIR, { recursive: true });
|
await mkdir(process.env.OCAS_DIR, { recursive: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await rm(tmpDir, { recursive: true, force: true });
|
await rm(tmpDir, { recursive: true, force: true });
|
||||||
if (originalEnv === undefined) {
|
if (originalEnv === undefined) {
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.UNCAGED_CAS_DIR = originalEnv;
|
process.env.OCAS_DIR = originalEnv;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const DETAIL_SCHEMA = {
|
|||||||
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
||||||
const casDir = join(storageRoot, "cas");
|
const casDir = join(storageRoot, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
return createUwfStore(storageRoot);
|
return createUwfStore(storageRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ async function setupSuspendedThread(mode: MockAgentMode): Promise<{
|
|||||||
cwd: tmpDir,
|
cwd: tmpDir,
|
||||||
});
|
});
|
||||||
|
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
await seedThreads(tmpDir, { [THREAD_ID]: startHash });
|
await seedThreads(tmpDir, { [THREAD_ID]: startHash });
|
||||||
|
|
||||||
const outputHash = await store.put(outputSchemaHash, {
|
const outputHash = await store.put(outputSchemaHash, {
|
||||||
@@ -189,7 +189,7 @@ function runUwf(
|
|||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
WORKFLOW_STORAGE_ROOT: tmpDir,
|
WORKFLOW_STORAGE_ROOT: tmpDir,
|
||||||
UNCAGED_CAS_DIR: casDir,
|
OCAS_DIR: casDir,
|
||||||
},
|
},
|
||||||
cwd: tmpDir,
|
cwd: tmpDir,
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
@@ -241,7 +241,7 @@ describe("uwf thread resume", () => {
|
|||||||
cwd: tmpDir,
|
cwd: tmpDir,
|
||||||
});
|
});
|
||||||
|
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
await seedThreads(tmpDir, { [THREAD_ID]: startHash });
|
await seedThreads(tmpDir, { [THREAD_ID]: startHash });
|
||||||
|
|
||||||
const result = runUwf(["thread", "resume", THREAD_ID], casDir);
|
const result = runUwf(["thread", "resume", THREAD_ID], casDir);
|
||||||
@@ -250,9 +250,9 @@ describe("uwf thread resume", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("resume suspended thread executes step and becomes idle", async () => {
|
test("resume suspended thread executes step and becomes idle", async () => {
|
||||||
const originalCasDir = process.env.UNCAGED_CAS_DIR;
|
const originalCasDir = process.env.OCAS_DIR;
|
||||||
const { casDir, mockAgentPath } = await setupSuspendedThread("ok");
|
const { casDir, mockAgentPath } = await setupSuspendedThread("ok");
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = runUwf(["thread", "resume", THREAD_ID, "--agent", mockAgentPath], casDir);
|
const result = runUwf(["thread", "resume", THREAD_ID, "--agent", mockAgentPath], casDir);
|
||||||
@@ -278,17 +278,17 @@ describe("uwf thread resume", () => {
|
|||||||
expect(showResult.suspendMessage).toBeNull();
|
expect(showResult.suspendMessage).toBeNull();
|
||||||
} finally {
|
} finally {
|
||||||
if (originalCasDir === undefined) {
|
if (originalCasDir === undefined) {
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.UNCAGED_CAS_DIR = originalCasDir;
|
process.env.OCAS_DIR = originalCasDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("resume without -p uses suspend message as agent prompt", async () => {
|
test("resume without -p uses suspend message as agent prompt", async () => {
|
||||||
const originalCasDir = process.env.UNCAGED_CAS_DIR;
|
const originalCasDir = process.env.OCAS_DIR;
|
||||||
const { casDir, mockAgentPath, promptCapturePath } = await setupSuspendedThread("ok");
|
const { casDir, mockAgentPath, promptCapturePath } = await setupSuspendedThread("ok");
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = runUwf(["thread", "resume", THREAD_ID, "--agent", mockAgentPath], casDir);
|
const result = runUwf(["thread", "resume", THREAD_ID, "--agent", mockAgentPath], casDir);
|
||||||
@@ -298,17 +298,17 @@ describe("uwf thread resume", () => {
|
|||||||
expect(capturedPrompt).toBe(SUSPEND_MESSAGE);
|
expect(capturedPrompt).toBe(SUSPEND_MESSAGE);
|
||||||
} finally {
|
} finally {
|
||||||
if (originalCasDir === undefined) {
|
if (originalCasDir === undefined) {
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.UNCAGED_CAS_DIR = originalCasDir;
|
process.env.OCAS_DIR = originalCasDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("resume with -p appends supplementary info to agent prompt", async () => {
|
test("resume with -p appends supplementary info to agent prompt", async () => {
|
||||||
const originalCasDir = process.env.UNCAGED_CAS_DIR;
|
const originalCasDir = process.env.OCAS_DIR;
|
||||||
const { casDir, mockAgentPath, promptCapturePath } = await setupSuspendedThread("ok");
|
const { casDir, mockAgentPath, promptCapturePath } = await setupSuspendedThread("ok");
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const supplement = "Use the REST API.";
|
const supplement = "Use the REST API.";
|
||||||
@@ -322,17 +322,17 @@ describe("uwf thread resume", () => {
|
|||||||
expect(capturedPrompt).toBe(`${SUSPEND_MESSAGE}\n\n${supplement}`);
|
expect(capturedPrompt).toBe(`${SUSPEND_MESSAGE}\n\n${supplement}`);
|
||||||
} finally {
|
} finally {
|
||||||
if (originalCasDir === undefined) {
|
if (originalCasDir === undefined) {
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.UNCAGED_CAS_DIR = originalCasDir;
|
process.env.OCAS_DIR = originalCasDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("multiple suspend/resume cycles", async () => {
|
test("multiple suspend/resume cycles", async () => {
|
||||||
const originalCasDir = process.env.UNCAGED_CAS_DIR;
|
const originalCasDir = process.env.OCAS_DIR;
|
||||||
const { casDir, mockAgentPath, promptCapturePath } = await setupSuspendedThread("suspend");
|
const { casDir, mockAgentPath, promptCapturePath } = await setupSuspendedThread("suspend");
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const firstResult = runUwf(["thread", "resume", THREAD_ID, "--agent", mockAgentPath], casDir);
|
const firstResult = runUwf(["thread", "resume", THREAD_ID, "--agent", mockAgentPath], casDir);
|
||||||
@@ -370,9 +370,9 @@ describe("uwf thread resume", () => {
|
|||||||
expect(capturedPrompt).toBe(SUSPEND_MESSAGE);
|
expect(capturedPrompt).toBe(SUSPEND_MESSAGE);
|
||||||
} finally {
|
} finally {
|
||||||
if (originalCasDir === undefined) {
|
if (originalCasDir === undefined) {
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.UNCAGED_CAS_DIR = originalCasDir;
|
process.env.OCAS_DIR = originalCasDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -305,8 +305,8 @@ describe("thread show status field", () => {
|
|||||||
await setupTestEnv();
|
await setupTestEnv();
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
const originalCasDir = process.env.UNCAGED_CAS_DIR;
|
const originalCasDir = process.env.OCAS_DIR;
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const workflowPath = join(tmpDir, "test-suspend-status.yaml");
|
const workflowPath = join(tmpDir, "test-suspend-status.yaml");
|
||||||
@@ -331,9 +331,9 @@ describe("thread show status field", () => {
|
|||||||
expect(result.thread).toBe(threadId);
|
expect(result.thread).toBe(threadId);
|
||||||
} finally {
|
} finally {
|
||||||
if (originalCasDir === undefined) {
|
if (originalCasDir === undefined) {
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.UNCAGED_CAS_DIR = originalCasDir;
|
process.env.OCAS_DIR = originalCasDir;
|
||||||
}
|
}
|
||||||
await teardown();
|
await teardown();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ describe("thread start --cwd CLI option", () => {
|
|||||||
await mkdir(storageRoot, { recursive: true });
|
await mkdir(storageRoot, { recursive: true });
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
|
|
||||||
// Set UNCAGED_CAS_DIR for this test
|
// Set OCAS_DIR for this test
|
||||||
originalEnv = process.env.UNCAGED_CAS_DIR;
|
originalEnv = process.env.OCAS_DIR;
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function teardown() {
|
async function teardown() {
|
||||||
@@ -31,9 +31,9 @@ describe("thread start --cwd CLI option", () => {
|
|||||||
}
|
}
|
||||||
// Restore original environment
|
// Restore original environment
|
||||||
if (originalEnv === undefined) {
|
if (originalEnv === undefined) {
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.UNCAGED_CAS_DIR = originalEnv;
|
process.env.OCAS_DIR = originalEnv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ graph:
|
|||||||
|
|
||||||
// Register the workflow
|
// Register the workflow
|
||||||
execFileSync("bun", [uwfBin, "workflow", "add", workflowPath], {
|
execFileSync("bun", [uwfBin, "workflow", "add", workflowPath], {
|
||||||
env: { ...process.env, UWF_STORAGE_ROOT: storageRoot, UNCAGED_CAS_DIR: casDir },
|
env: { ...process.env, UWF_STORAGE_ROOT: storageRoot, OCAS_DIR: casDir },
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ graph:
|
|||||||
"bun",
|
"bun",
|
||||||
[uwfBin, "thread", "start", "test-cwd-cli", "-p", "test prompt", "--cwd", testCwd],
|
[uwfBin, "thread", "start", "test-cwd-cli", "-p", "test prompt", "--cwd", testCwd],
|
||||||
{
|
{
|
||||||
env: { ...process.env, UWF_STORAGE_ROOT: storageRoot, UNCAGED_CAS_DIR: casDir },
|
env: { ...process.env, UWF_STORAGE_ROOT: storageRoot, OCAS_DIR: casDir },
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
|
|||||||
test("thread exec records suspend step in CAS and suspend metadata in threads.yaml", async () => {
|
test("thread exec records suspend step in CAS and suspend metadata in threads.yaml", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
const originalCasDir = process.env.UNCAGED_CAS_DIR;
|
const originalCasDir = process.env.OCAS_DIR;
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const store = createFsStore(casDir);
|
const store = createFsStore(casDir);
|
||||||
@@ -128,7 +128,7 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
|
|||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
WORKFLOW_STORAGE_ROOT: tmpDir,
|
WORKFLOW_STORAGE_ROOT: tmpDir,
|
||||||
UNCAGED_CAS_DIR: casDir,
|
OCAS_DIR: casDir,
|
||||||
},
|
},
|
||||||
cwd: tmpDir,
|
cwd: tmpDir,
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
@@ -169,9 +169,9 @@ describe("suspend step CAS chain and threads.yaml metadata", () => {
|
|||||||
expect(showResult.suspendedRole).toBe("worker");
|
expect(showResult.suspendedRole).toBe("worker");
|
||||||
} finally {
|
} finally {
|
||||||
if (originalCasDir === undefined) {
|
if (originalCasDir === undefined) {
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.UNCAGED_CAS_DIR = originalCasDir;
|
process.env.OCAS_DIR = originalCasDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ describe("suspended thread display", () => {
|
|||||||
test("thread list shows [suspended] marker for suspended threads", async () => {
|
test("thread list shows [suspended] marker for suspended threads", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
const originalCasDir = process.env.UNCAGED_CAS_DIR;
|
const originalCasDir = process.env.OCAS_DIR;
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const uwf = await createUwfStore(tmpDir);
|
const uwf = await createUwfStore(tmpDir);
|
||||||
@@ -131,9 +131,9 @@ describe("suspended thread display", () => {
|
|||||||
expect(idleItem!.statusDisplay).toBe("idle");
|
expect(idleItem!.statusDisplay).toBe("idle");
|
||||||
} finally {
|
} finally {
|
||||||
if (originalCasDir === undefined) {
|
if (originalCasDir === undefined) {
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.UNCAGED_CAS_DIR = originalCasDir;
|
process.env.OCAS_DIR = originalCasDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -141,8 +141,8 @@ describe("suspended thread display", () => {
|
|||||||
test("thread show displays suspend info and resume hint", async () => {
|
test("thread show displays suspend info and resume hint", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
const originalCasDir = process.env.UNCAGED_CAS_DIR;
|
const originalCasDir = process.env.OCAS_DIR;
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const uwf = await createUwfStore(tmpDir);
|
const uwf = await createUwfStore(tmpDir);
|
||||||
@@ -219,9 +219,9 @@ describe("suspended thread display", () => {
|
|||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (originalCasDir === undefined) {
|
if (originalCasDir === undefined) {
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.UNCAGED_CAS_DIR = originalCasDir;
|
process.env.OCAS_DIR = originalCasDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -229,8 +229,8 @@ describe("suspended thread display", () => {
|
|||||||
test("non-suspended threads do not show suspend markers or hints", async () => {
|
test("non-suspended threads do not show suspend markers or hints", async () => {
|
||||||
const casDir = join(tmpDir, "cas");
|
const casDir = join(tmpDir, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
const originalCasDir = process.env.UNCAGED_CAS_DIR;
|
const originalCasDir = process.env.OCAS_DIR;
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const uwf = await createUwfStore(tmpDir);
|
const uwf = await createUwfStore(tmpDir);
|
||||||
@@ -278,9 +278,9 @@ describe("suspended thread display", () => {
|
|||||||
expect(threadItem!.statusDisplay).toBe("idle");
|
expect(threadItem!.statusDisplay).toBe("idle");
|
||||||
} finally {
|
} finally {
|
||||||
if (originalCasDir === undefined) {
|
if (originalCasDir === undefined) {
|
||||||
delete process.env.UNCAGED_CAS_DIR;
|
delete process.env.OCAS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.UNCAGED_CAS_DIR = originalCasDir;
|
process.env.OCAS_DIR = originalCasDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const DETAIL_SCHEMA = {
|
|||||||
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
||||||
const casDir = join(storageRoot, "cas");
|
const casDir = join(storageRoot, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
return createUwfStore(storageRoot);
|
return createUwfStore(storageRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { createUwfStore, saveWorkflowRegistry } from "../store.js";
|
|||||||
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
||||||
const casDir = join(storageRoot, "cas");
|
const casDir = join(storageRoot, "cas");
|
||||||
await mkdir(casDir, { recursive: true });
|
await mkdir(casDir, { recursive: true });
|
||||||
process.env.UNCAGED_CAS_DIR = casDir;
|
process.env.OCAS_DIR = casDir;
|
||||||
return createUwfStore(storageRoot);
|
return createUwfStore(storageRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export function getDefaultStorageRoot(): string {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve storage root.
|
* Resolve storage root.
|
||||||
* Priority: `UWF_STORAGE_ROOT` → `WORKFLOW_STORAGE_ROOT` → `UNCAGED_WORKFLOW_STORAGE_ROOT` (legacy) → default.
|
* Priority: `UWF_STORAGE_ROOT` → `WORKFLOW_STORAGE_ROOT` → default.
|
||||||
*/
|
*/
|
||||||
export function resolveStorageRoot(): string {
|
export function resolveStorageRoot(): string {
|
||||||
const primary = process.env.UWF_STORAGE_ROOT;
|
const primary = process.env.UWF_STORAGE_ROOT;
|
||||||
@@ -130,10 +130,6 @@ export function resolveStorageRoot(): string {
|
|||||||
if (userOverride !== undefined && userOverride !== "") {
|
if (userOverride !== undefined && userOverride !== "") {
|
||||||
return userOverride;
|
return userOverride;
|
||||||
}
|
}
|
||||||
const legacy = process.env.UNCAGED_WORKFLOW_STORAGE_ROOT;
|
|
||||||
if (legacy !== undefined && legacy !== "") {
|
|
||||||
return legacy;
|
|
||||||
}
|
|
||||||
return getDefaultStorageRoot();
|
return getDefaultStorageRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +141,7 @@ export function migrateStorageIfNeeded(home: string = homedir()): void {
|
|||||||
if (!existsSync(newPath) && existsSync(oldPath)) {
|
if (!existsSync(newPath) && existsSync(oldPath)) {
|
||||||
symlinkSync(oldPath, newPath);
|
symlinkSync(oldPath, newPath);
|
||||||
// biome-ignore lint/suspicious/noConsole: migration notice
|
// biome-ignore lint/suspicious/noConsole: migration notice
|
||||||
console.log("⚠️ Storage migrated: ~/.uwf → ~/.uncaged/workflow (symlink)");
|
console.log("⚠️ Storage linked: ~/.uwf → legacy workflow directory (symlink)");
|
||||||
// biome-ignore lint/suspicious/noConsole: migration notice
|
// biome-ignore lint/suspicious/noConsole: migration notice
|
||||||
console.log(
|
console.log(
|
||||||
" This symlink is temporary. Copy your data to ~/.uwf/ and remove the symlink in a future version.",
|
" This symlink is temporary. Copy your data to ~/.uwf/ and remove the symlink in a future version.",
|
||||||
@@ -157,7 +153,7 @@ export function migrateStorageIfNeeded(home: string = homedir()): void {
|
|||||||
if (!existsSync(newCas) && existsSync(oldCas)) {
|
if (!existsSync(newCas) && existsSync(oldCas)) {
|
||||||
symlinkSync(oldCas, newCas);
|
symlinkSync(oldCas, newCas);
|
||||||
// biome-ignore lint/suspicious/noConsole: migration notice
|
// biome-ignore lint/suspicious/noConsole: migration notice
|
||||||
console.log("⚠️ CAS storage migrated: ~/.ocas → ~/.uncaged/json-cas (symlink)");
|
console.log("⚠️ CAS storage linked: ~/.ocas → legacy CAS directory (symlink)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,17 +167,13 @@ export function getCasDir(storageRoot: string): string {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the global CAS directory shared by all uwf and ocas tools.
|
* Returns the global CAS directory shared by all uwf and ocas tools.
|
||||||
* Priority: `OCAS_DIR` → `UNCAGED_CAS_DIR` (legacy) → default ~/.ocas
|
* Priority: `OCAS_DIR` → default ~/.ocas
|
||||||
*/
|
*/
|
||||||
export function getGlobalCasDir(): string {
|
export function getGlobalCasDir(): string {
|
||||||
const primary = process.env.OCAS_DIR;
|
const primary = process.env.OCAS_DIR;
|
||||||
if (primary !== undefined && primary !== "") {
|
if (primary !== undefined && primary !== "") {
|
||||||
return primary;
|
return primary;
|
||||||
}
|
}
|
||||||
const legacy = process.env.UNCAGED_CAS_DIR;
|
|
||||||
if (legacy !== undefined && legacy !== "") {
|
|
||||||
return legacy;
|
|
||||||
}
|
|
||||||
return join(homedir(), ".ocas");
|
return join(homedir(), ".ocas");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,12 +31,12 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.shazhou.work/uncaged/workflow.git",
|
"url": "https://git.shazhou.work/shazhou/united-workforce.git",
|
||||||
"directory": "packages/protocol"
|
"directory": "packages/protocol"
|
||||||
},
|
},
|
||||||
"homepage": "https://git.shazhou.work/uncaged/workflow#readme",
|
"homepage": "https://git.shazhou.work/shazhou/united-workforce#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://git.shazhou.work/uncaged/workflow/issues"
|
"url": "https://git.shazhou.work/shazhou/united-workforce/issues"
|
||||||
},
|
},
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,4 +180,4 @@ src/
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Reads `config.yaml` and `.env` from the workflow storage root (`~/.uncaged/workflow` by default). See `@united-workforce/protocol` for `WorkflowConfig` shape. Set via `uwf setup`.
|
Reads `config.yaml` and `.env` from the workflow storage root (`~/.uwf` by default). See `@united-workforce/protocol` for `WorkflowConfig` shape. Set via `uwf setup`.
|
||||||
|
|||||||
@@ -35,12 +35,12 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.shazhou.work/uncaged/workflow.git",
|
"url": "https://git.shazhou.work/shazhou/united-workforce.git",
|
||||||
"directory": "packages/util-agent"
|
"directory": "packages/util-agent"
|
||||||
},
|
},
|
||||||
"homepage": "https://git.shazhou.work/uncaged/workflow#readme",
|
"homepage": "https://git.shazhou.work/shazhou/united-workforce#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://git.shazhou.work/uncaged/workflow/issues"
|
"url": "https://git.shazhou.work/shazhou/united-workforce/issues"
|
||||||
},
|
},
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function getDefaultStorageRoot(): string {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve storage root.
|
* Resolve storage root.
|
||||||
* Priority: `UWF_STORAGE_ROOT` → `WORKFLOW_STORAGE_ROOT` → `UNCAGED_WORKFLOW_STORAGE_ROOT` (legacy) → default.
|
* Priority: `UWF_STORAGE_ROOT` → `WORKFLOW_STORAGE_ROOT` → default.
|
||||||
*/
|
*/
|
||||||
export function resolveStorageRoot(): string {
|
export function resolveStorageRoot(): string {
|
||||||
const primary = process.env.UWF_STORAGE_ROOT;
|
const primary = process.env.UWF_STORAGE_ROOT;
|
||||||
@@ -40,10 +40,6 @@ export function resolveStorageRoot(): string {
|
|||||||
if (userOverride !== undefined && userOverride !== "") {
|
if (userOverride !== undefined && userOverride !== "") {
|
||||||
return userOverride;
|
return userOverride;
|
||||||
}
|
}
|
||||||
const legacy = process.env.UNCAGED_WORKFLOW_STORAGE_ROOT;
|
|
||||||
if (legacy !== undefined && legacy !== "") {
|
|
||||||
return legacy;
|
|
||||||
}
|
|
||||||
return getDefaultStorageRoot();
|
return getDefaultStorageRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,17 +59,13 @@ const THREAD_VAR_PREFIX = "@uwf/thread/";
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Global CAS directory (same as uwf CLI).
|
* Global CAS directory (same as uwf CLI).
|
||||||
* Priority: `OCAS_DIR` → `UNCAGED_CAS_DIR` (legacy) → default ~/.ocas
|
* Priority: `OCAS_DIR` → default ~/.ocas
|
||||||
*/
|
*/
|
||||||
export function getGlobalCasDir(): string {
|
export function getGlobalCasDir(): string {
|
||||||
const primary = process.env.OCAS_DIR;
|
const primary = process.env.OCAS_DIR;
|
||||||
if (primary !== undefined && primary !== "") {
|
if (primary !== undefined && primary !== "") {
|
||||||
return primary;
|
return primary;
|
||||||
}
|
}
|
||||||
const legacy = process.env.UNCAGED_CAS_DIR;
|
|
||||||
if (legacy !== undefined && legacy !== "") {
|
|
||||||
return legacy;
|
|
||||||
}
|
|
||||||
return join(homedir(), ".ocas");
|
return join(homedir(), ".ocas");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ src/
|
|||||||
├── frontmatter-markdown/ Parse and validate agent frontmatter
|
├── frontmatter-markdown/ Parse and validate agent frontmatter
|
||||||
├── refs-field.ts Normalize refs arrays on CAS nodes
|
├── refs-field.ts Normalize refs arrays on CAS nodes
|
||||||
├── result.ts ok / err helpers
|
├── result.ts ok / err helpers
|
||||||
├── storage-root.ts Default ~/.uncaged/workflow paths
|
├── storage-root.ts Default ~/.uwf paths
|
||||||
├── env.ts Environment variable helper
|
├── env.ts Environment variable helper
|
||||||
├── cli-reference.ts Markdown CLI reference generator
|
├── cli-reference.ts Markdown CLI reference generator
|
||||||
└── types.ts LogFn, Result, logger options
|
└── types.ts LogFn, Result, logger options
|
||||||
@@ -143,4 +143,4 @@ src/
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
`getDefaultWorkflowStorageRoot()` resolves to `~/.uncaged/workflow` unless overridden by environment (see `storage-root.ts`).
|
`getDefaultWorkflowStorageRoot()` resolves to `~/.uwf` unless overridden by environment (see `storage-root.ts`).
|
||||||
|
|||||||
@@ -28,12 +28,12 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.shazhou.work/uncaged/workflow.git",
|
"url": "https://git.shazhou.work/shazhou/united-workforce.git",
|
||||||
"directory": "packages/util"
|
"directory": "packages/util"
|
||||||
},
|
},
|
||||||
"homepage": "https://git.shazhou.work/uncaged/workflow#readme",
|
"homepage": "https://git.shazhou.work/shazhou/united-workforce#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://git.shazhou.work/uncaged/workflow/issues"
|
"url": "https://git.shazhou.work/shazhou/united-workforce/issues"
|
||||||
},
|
},
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
export function generateBootstrapReference(): string {
|
export function generateBootstrapReference(): string {
|
||||||
return `---
|
return `---
|
||||||
name: uwf
|
name: uwf
|
||||||
description: "Uncaged Workflow (uwf) — YAML 状态机工作流引擎。任务涉及 workflow 时加载此 skill。"
|
description: "United Workforce (uwf) — YAML 状态机工作流引擎。任务涉及 workflow 时加载此 skill。"
|
||||||
tags: [workflow, uwf, uncaged]
|
tags: [workflow, uwf]
|
||||||
triggers:
|
triggers:
|
||||||
- uwf
|
- uwf
|
||||||
- workflow
|
- workflow
|
||||||
- 工作流
|
- 工作流
|
||||||
---
|
---
|
||||||
|
|
||||||
# uwf (Uncaged Workflow)
|
# uwf (United Workforce)
|
||||||
|
|
||||||
YAML 状态机工作流引擎。当用户提到「workflow」「工作流」时,指的是 **uwf workflow**(YAML 定义的状态机),不是 Hermes skill。用 \`uwf\` CLI 操作,不要混淆。
|
YAML 状态机工作流引擎。当用户提到「workflow」「工作流」时,指的是 **uwf workflow**(YAML 定义的状态机),不是 Hermes skill。用 \`uwf\` CLI 操作,不要混淆。
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
# Examples:
|
# Examples:
|
||||||
# ./scripts/batch-solve.sh 448 449
|
# ./scripts/batch-solve.sh 448 449
|
||||||
# ./scripts/batch-solve.sh --agent "bun run $(pwd)/packages/agent-claude-code/src/cli.ts" 448 449
|
# ./scripts/batch-solve.sh --agent "bun run $(pwd)/packages/agent-claude-code/src/cli.ts" 448 449
|
||||||
# ./scripts/batch-solve.sh --repo uncaged/workflow --count 15 448 449
|
# ./scripts/batch-solve.sh --repo shazhou/united-workforce --count 15 448 449
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
AGENT=""
|
AGENT=""
|
||||||
REPO="uncaged/workflow"
|
REPO="shazhou/united-workforce"
|
||||||
COUNT=10
|
COUNT=10
|
||||||
ISSUES=()
|
ISSUES=()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Check development environment prerequisites for uncaged/workflow.
|
# Check development environment prerequisites for shazhou/united-workforce.
|
||||||
# Non-interactive — prints actionable fix instructions on failure.
|
# Non-interactive — prints actionable fix instructions on failure.
|
||||||
# Exit 0 = all good, exit 1 = missing dependencies.
|
# Exit 0 = all good, exit 1 = missing dependencies.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -54,7 +54,7 @@ echo "=== Workflow ==="
|
|||||||
REPO_DIR="${WORKFLOW_REPO:-$(cd "$(dirname "$0")/.." && pwd)}"
|
REPO_DIR="${WORKFLOW_REPO:-$(cd "$(dirname "$0")/.." && pwd)}"
|
||||||
check "repo at ~/repos/workflow or WORKFLOW_REPO set" \
|
check "repo at ~/repos/workflow or WORKFLOW_REPO set" \
|
||||||
"[ -f '$REPO_DIR/packages/cli/src/cli.ts' ]" \
|
"[ -f '$REPO_DIR/packages/cli/src/cli.ts' ]" \
|
||||||
"Clone the repo: git clone https://git.shazhou.work/uncaged/workflow ~/repos/workflow"
|
"Clone the repo: git clone https://git.shazhou.work/shazhou/united-workforce ~/repos/workflow"
|
||||||
|
|
||||||
# Check bun install
|
# Check bun install
|
||||||
check "node_modules installed" \
|
check "node_modules installed" \
|
||||||
@@ -89,7 +89,7 @@ echo ""
|
|||||||
echo "=== Config ==="
|
echo "=== Config ==="
|
||||||
|
|
||||||
# Check workflow config exists
|
# Check workflow config exists
|
||||||
CONFIG_DIR="${UNCAGED_WORKFLOW_STORAGE_ROOT:-$HOME/.uncaged/workflow}"
|
CONFIG_DIR="${UWF_STORAGE_ROOT:-$HOME/.shazhou/united-workforce}"
|
||||||
check "config.yaml exists" \
|
check "config.yaml exists" \
|
||||||
"[ -f '$CONFIG_DIR/config.yaml' ]" \
|
"[ -f '$CONFIG_DIR/config.yaml' ]" \
|
||||||
"Run: uwf setup"
|
"Run: uwf setup"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# E2E walkthrough for uncaged/workflow.
|
# E2E walkthrough for shazhou/united-workforce.
|
||||||
# Runs inside Docker with isolated UNCAGED_WORKFLOW_STORAGE_ROOT.
|
# Runs inside Docker with isolated UWF_STORAGE_ROOT.
|
||||||
# Exercises: setup → workflow add → thread start/exec → cancel/fork → read/inspect.
|
# Exercises: setup → workflow add → thread start/exec → cancel/fork → read/inspect.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
@@ -69,9 +69,9 @@ cat > "$E2E_DIR/run.sh" << 'INNER_SCRIPT'
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Isolated storage — never touches host's ~/.uncaged/workflow
|
# Isolated storage — never touches host's ~/.uwf
|
||||||
export UNCAGED_WORKFLOW_STORAGE_ROOT="/tmp/uwf-e2e-storage"
|
export UWF_STORAGE_ROOT="/tmp/uwf-e2e-storage"
|
||||||
mkdir -p "$UNCAGED_WORKFLOW_STORAGE_ROOT"
|
mkdir -p "$UWF_STORAGE_ROOT"
|
||||||
|
|
||||||
REPO_DIR="$1"
|
REPO_DIR="$1"
|
||||||
AGENT="$2"
|
AGENT="$2"
|
||||||
@@ -156,8 +156,8 @@ if [ -n "$PROVIDER" ] && [ -n "$MODEL" ] && [ -n "$API_KEY" ]; then
|
|||||||
run_test "uwf setup (non-interactive)" bash -c "$SETUP_CMD"
|
run_test "uwf setup (non-interactive)" bash -c "$SETUP_CMD"
|
||||||
else
|
else
|
||||||
# Copy host config if available
|
# Copy host config if available
|
||||||
if [ -f "$HOME/.uncaged/workflow/config.yaml" ]; then
|
if [ -f "$HOME/.shazhou/united-workforce/config.yaml" ]; then
|
||||||
cp "$HOME/.uncaged/workflow/config.yaml" "$UNCAGED_WORKFLOW_STORAGE_ROOT/config.yaml"
|
cp "$HOME/.shazhou/united-workforce/config.yaml" "$UWF_STORAGE_ROOT/config.yaml"
|
||||||
echo " Copied host config.yaml" >&2
|
echo " Copied host config.yaml" >&2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
+3
-3
@@ -1,14 +1,14 @@
|
|||||||
---
|
---
|
||||||
name: uwf
|
name: uwf
|
||||||
description: "Uncaged Workflow (uwf) — YAML 状态机工作流引擎。任务涉及 workflow 时加载此 skill。"
|
description: "United Workforce (uwf) — YAML 状态机工作流引擎。任务涉及 workflow 时加载此 skill。"
|
||||||
tags: [workflow, uwf, uncaged]
|
tags: [workflow, uwf]
|
||||||
triggers:
|
triggers:
|
||||||
- uwf
|
- uwf
|
||||||
- workflow
|
- workflow
|
||||||
- 工作流
|
- 工作流
|
||||||
---
|
---
|
||||||
|
|
||||||
# uwf (Uncaged Workflow)
|
# uwf (United Workforce)
|
||||||
|
|
||||||
YAML 状态机工作流引擎。当用户提到「workflow」「工作流」时,指的是 **uwf workflow**(YAML 定义的状态机),不是 Hermes skill。用 `uwf` CLI 操作,不要混淆。
|
YAML 状态机工作流引擎。当用户提到「workflow」「工作流」时,指的是 **uwf workflow**(YAML 定义的状态机),不是 Hermes skill。用 `uwf` CLI 操作,不要混淆。
|
||||||
|
|
||||||
|
|||||||
@@ -742,7 +742,7 @@ roles:
|
|||||||
6. Private packages (server, frontend, tools) must have `"private": true` and can skip publishConfig/files
|
6. Private packages (server, frontend, tools) must have `"private": true` and can skip publishConfig/files
|
||||||
7. Each package should have a `"scripts"` section with at least `"test"` if tests exist
|
7. Each package should have a `"scripts"` section with at least `"test"` if tests exist
|
||||||
8. Workspace dependencies should use `"workspace:^"` protocol, not version numbers
|
8. Workspace dependencies should use `"workspace:^"` protocol, not version numbers
|
||||||
- Check: `grep -r '"@uncaged/' packages/*/package.json | grep -v 'workspace:'`
|
- Check: `grep -r '"@united-workforce/' packages/*/package.json | grep -v 'workspace:'`
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
You MUST actually run each command below and include real output. Do NOT guess or fabricate results.
|
You MUST actually run each command below and include real output. Do NOT guess or fabricate results.
|
||||||
@@ -764,7 +764,7 @@ roles:
|
|||||||
"
|
"
|
||||||
done
|
done
|
||||||
# No hardcoded workspace deps
|
# No hardcoded workspace deps
|
||||||
! grep -r '"@uncaged/' packages/*/package.json | grep -v 'workspace:' | grep -v node_modules
|
! grep -r '"@united-workforce/' packages/*/package.json | grep -v 'workspace:' | grep -v node_modules
|
||||||
```
|
```
|
||||||
|
|
||||||
Post-condition: Verification script shows OK for all non-private packages, no hardcoded workspace versions.
|
Post-condition: Verification script shows OK for all non-private packages, no hardcoded workspace versions.
|
||||||
@@ -849,4 +849,4 @@ roles:
|
|||||||
type: string
|
type: string
|
||||||
capabilities:
|
capabilities:
|
||||||
- workflow-config
|
- workflow-config
|
||||||
description: Normalize an existing project to @uncaged bun monorepo conventions. Supports both TypeScript and JS/MJS projects. Each role handles one configuration layer. All roles allow fail.
|
description: Normalize an existing project to @united-workforce bun monorepo conventions. Supports both TypeScript and JS/MJS projects. Each role handles one configuration layer. All roles allow fail.
|
||||||
@@ -148,7 +148,7 @@ roles:
|
|||||||
|
|
||||||
- Crockford Base32 log tags (8-char, unique per call site)
|
- Crockford Base32 log tags (8-char, unique per call site)
|
||||||
|
|
||||||
- No `console.log` in production code (use createLogger from @uncaged/util)
|
- No `console.log` in production code (use createLogger from @united-workforce/util)
|
||||||
|
|
||||||
- No dynamic imports in production code
|
- No dynamic imports in production code
|
||||||
|
|
||||||
@@ -247,7 +247,7 @@ roles:
|
|||||||
\ create a new branch.\n1. Stage all changes: `git add -A`\n2. Commit with a descriptive message referencing the issue: `git commit -m \"type: description\\n\\nFixes #N\"`\n3. Push the branch: `git\
|
\ create a new branch.\n1. Stage all changes: `git add -A`\n2. Commit with a descriptive message referencing the issue: `git commit -m \"type: description\\n\\nFixes #N\"`\n3. Push the branch: `git\
|
||||||
\ push -u origin <branch-name>`\n4. **Verify push succeeded** — run `git ls-remote origin <branch-name>` and confirm it prints a commit hash.\n - If no output or push failed: capture the error, mark hook_failed\n\
|
\ push -u origin <branch-name>`\n4. **Verify push succeeded** — run `git ls-remote origin <branch-name>` and confirm it prints a commit hash.\n - If no output or push failed: capture the error, mark hook_failed\n\
|
||||||
5. Create a PR using the Gitea API (do NOT use `tea pr create` — it fails in worktrees):\n ```bash\n GITEA_TOKEN=$(cfg get GITEA_TOKEN)\n curl -s -X POST -H \"Authorization: token $GITEA_TOKEN\" -H \"Content-Type: application/json\" \\\n\
|
5. Create a PR using the Gitea API (do NOT use `tea pr create` — it fails in worktrees):\n ```bash\n GITEA_TOKEN=$(cfg get GITEA_TOKEN)\n curl -s -X POST -H \"Authorization: token $GITEA_TOKEN\" -H \"Content-Type: application/json\" \\\n\
|
||||||
\ \"https://git.shazhou.work/api/v1/repos/<owner>/<repo>/pulls\" \\\n -d '{\"title\":\"...\",\"body\":\"...\",\"head\":\"<branch>\",\"base\":\"main\"}'\n ```\n - The repo remote (owner/repo format, e.g. \"uncaged/workflow\") is given in your task prompt — use it directly.\n\
|
\ \"https://git.shazhou.work/api/v1/repos/<owner>/<repo>/pulls\" \\\n -d '{\"title\":\"...\",\"body\":\"...\",\"head\":\"<branch>\",\"base\":\"main\"}'\n ```\n - The repo remote (owner/repo format, e.g. \"shazhou/united-workforce\") is given in your task prompt — use it directly.\n\
|
||||||
\ - PR body must include: What / Why / Changes / Ref sections, with `Fixes #N` in Ref\n6. **Verify PR was created** — parse the curl response JSON: it must contain a `\"number\"` field. Print the PR URL.\n\
|
\ - PR body must include: What / Why / Changes / Ref sections, with `Fixes #N` in Ref\n6. **Verify PR was created** — parse the curl response JSON: it must contain a `\"number\"` field. Print the PR URL.\n\
|
||||||
\ - If curl returns an error or no number field: capture the response, mark hook_failed\n7. After PR creation, clean up the worktree:\n - cd to the repo root (parent of .worktrees)\n - `git worktree remove <worktree-path>`"
|
\ - If curl returns an error or no number field: capture the response, mark hook_failed\n7. After PR creation, clean up the worktree:\n - cd to the repo root (parent of .worktrees)\n - `git worktree remove <worktree-path>`"
|
||||||
output: Include PR URL on success or error log on failure. Set $status to committed (with prUrl) or hook_failed (with error).
|
output: Include PR URL on success or error log on failure. Set $status to committed (with prUrl) or hook_failed (with error).
|
||||||
|
|||||||
Reference in New Issue
Block a user